CI框架下column注入

栏目: 数据库 · 发布时间: 6年前

内容简介:CI框架下column注入

TCTF Web有大量的审计题,其中有很多有趣又get到新姿势的。其中有一道关于CI框架下的注入的问题。

具体注入点在CI的select()中,查看官方文档,我们知道,select()函数选中一个列名,然后经过处理拼接到 sql 语句中,而这个处理本身来说就存在着问题。

而关于列名的注入,本身来说有一定的鸡肋性质,开发者一般都不会让用户可控列名。

但是有些时候,开发者会偷懒定义一些奇怪的路由设定个API,将其中的关键词提取出来当作列名,比如user表中有id字段,对应的路由规则就是 http://xxxxxx/user/id/1.xxx 如果没有对user进行处理直接放入select就会导致注入。

CI框架下column注入

首先看一下select函数/system/database/DB_query_builder.php

public function select($select = '*', $escape = NULL)
	{
		if (is_string($select))
		{
			$select = explode(',', $select);
		}
		//var_dump($select);
		// If the escape value was not set, we will base it on the global setting
		is_bool($escape) OR $escape = $this->_protect_identifiers;

		foreach ($select as $val)
		{
			$val = trim($val);

			if ($val !== '')
			{
				$this->qb_select[] = $val;
				$this->qb_no_escape[] = $escape;

				if ($this->qb_caching === TRUE)
				{
					$this->qb_cache_select[] = $val;
					$this->qb_cache_exists[] = 'select';
					$this->qb_cache_no_escape[] = $escape;
				}
			}
		}
		//var_dump($this->qb_select);
		return $this;
		
	}

CI框架下column注入

可以看到直接把结果存在qb_select数组中,所有这样的变量在最后语句执行时,被拼接起来形成sql语句。

所以,这里没有做任何处理,接下来看下(最终执行sql语句的函数)get()

/system/database/DB_query_builder.php

public function get($table = '', $limit = NULL, $offset = NULL)
	{
		if ($table !== '')
		{
			$this->_track_aliases($table);
			$this->from($table);
		}

		if ( ! empty($limit))
		{
			$this->limit($limit, $offset);
		}

		$result = $this->query($this->_compile_select());
		$this->_reset_select();
		return $result;
	}

这里也没有对刚才所说的数组进行处理,还要继续跟踪,看下_compile_select

贴一下关键位置

foreach ($this->qb_select as $key => $val)
				{
					$no_escape = isset($this->qb_no_escape[$key]) ? $this->qb_no_escape[$key] : NULL;
					
					$this->qb_select[$key] = $this->protect_identifiers($val, FALSE, $no_escape);
					
				}
				
				$sql .= implode(', ', $this->qb_select);

escape默认是1,紧接着把提取出来的非sql关键词带入到protect_identifiers进行转义简化

public function protect_identifiers($item, $prefix_single = FALSE, $protect_identifiers = NULL, $field_exists = TRUE)
	{
		if ( ! is_bool($protect_identifiers))
		{
			$protect_identifiers = $this->_protect_identifiers;
		}

		if (is_array($item))
		{
			$escaped_array = array();
			foreach ($item as $k => $v)
			{
				$escaped_array[$this->protect_identifiers($k)] = $this->protect_identifiers($v, $prefix_single, $protect_identifiers, $field_exists);
			}
			//var_dump($escaped_array);
			return $escaped_array;
		}

		// This is basically a bug fix for queries that use MAX, MIN, etc.
		// If a parenthesis is found we know that we do not need to
		// escape the data or add a prefix. There's probably a more graceful
		// way to deal with this, but I'm not thinking of it -- Rick
		//
		// Added exception for single quotes as well, we don't want to alter
		// literal strings. -- Narf
		if (strcspn($item, "()'") !== strlen($item))
		{
			return $item;
		}

		// Convert tabs or multiple spaces into single spaces
		$item = preg_replace('/\s+/', ' ', trim($item));

		// If the item has an alias declaration we remove it and set it aside.
		// Note: strripos() is used in order to support spaces in table names
		if ($offset = strripos($item, ' AS '))
		{
			$alias = ($protect_identifiers)
				? substr($item, $offset, 4).$this->escape_identifiers(substr($item, $offset + 4))
				: substr($item, $offset);
			$item = substr($item, 0, $offset);
		}
		elseif ($offset = strrpos($item, ' '))
		{
			$alias = ($protect_identifiers)
				? ' '.$this->escape_identifiers(substr($item, $offset + 1))
				: substr($item, $offset);
			$item = substr($item, 0, $offset);
		}
		else
		{
			$alias = '';
		}

		// Break the string apart if it contains periods, then insert the table prefix
		// in the correct location, assuming the period doesn't indicate that we're dealing
		// with an alias. While we're at it, we will escape the components
		if (strpos($item, '.') !== FALSE)
		{
			$parts = explode('.', $item);

			// Does the first segment of the exploded item match
			// one of the aliases previously identified? If so,
			// we have nothing more to do other than escape the item
			//
			// NOTE: The ! empty() condition prevents this method
			//       from breaking when QB isn't enabled.
			if ( ! empty($this->qb_aliased_tables) && in_array($parts[0], $this->qb_aliased_tables))
			{
				if ($protect_identifiers === TRUE)
				{
					foreach ($parts as $key => $val)
					{
						if ( ! in_array($val, $this->_reserved_identifiers))
						{
							$parts[$key] = $this->escape_identifiers($val);
						}
					}

					$item = implode('.', $parts);
				}

				return $item.$alias;
			}

			// Is there a table prefix defined in the config file? If not, no need to do anything
			if ($this->dbprefix !== '')
			{
				// We now add the table prefix based on some logic.
				// Do we have 4 segments (hostname.database.table.column)?
				// If so, we add the table prefix to the column name in the 3rd segment.
				if (isset($parts[3]))
				{
					$i = 2;
				}
				// Do we have 3 segments (database.table.column)?
				// If so, we add the table prefix to the column name in 2nd position
				elseif (isset($parts[2]))
				{
					$i = 1;
				}
				// Do we have 2 segments (table.column)?
				// If so, we add the table prefix to the column name in 1st segment
				else
				{
					$i = 0;
				}

				// This flag is set when the supplied $item does not contain a field name.
				// This can happen when this function is being called from a JOIN.
				if ($field_exists === FALSE)
				{
					$i++;
				}

				// Verify table prefix and replace if necessary
				if ($this->swap_pre !== '' && strpos($parts[$i], $this->swap_pre) === 0)
				{
					$parts[$i] = preg_replace('/^'.$this->swap_pre.'(\S+?)/', $this->dbprefix.'\\1', $parts[$i]);
				}
				// We only add the table prefix if it does not already exist
				elseif (strpos($parts[$i], $this->dbprefix) !== 0)
				{
					$parts[$i] = $this->dbprefix.$parts[$i];
				}

				// Put the parts back together
				$item = implode('.', $parts);
			}

			if ($protect_identifiers === TRUE)
			{
				$item = $this->escape_identifiers($item);
			}

			return $item.$alias;
		}

		// Is there a table prefix? If not, no need to insert it
		if ($this->dbprefix !== '')
		{
			// Verify table prefix and replace if necessary
			if ($this->swap_pre !== '' && strpos($item, $this->swap_pre) === 0)
			{
				$item = preg_replace('/^'.$this->swap_pre.'(\S+?)/', $this->dbprefix.'\\1', $item);
			}
			// Do we prefix an item with no segments?
			elseif ($prefix_single === TRUE && strpos($item, $this->dbprefix) !== 0)
			{
				$item = $this->dbprefix.$item;
			}
		}

		if ($protect_identifiers === TRUE && ! in_array($item, $this->_reserved_identifiers))
		{
			$item = $this->escape_identifiers($item);
		}

		return $item.$alias;
	}

CI框架下column注入

如果匹配到()'其中任意字符,直接返回原部分语句;

CI框架下column注入

trim处理

CI框架下column注入

匹配到AS则把$item去掉最后面的AS及AS之后的部分,AS之后的部分做escape_identifiers处理

CI框架下column注入

匹配到空格把最后的空格及其后面的部分拿出来从item去掉并做escape_identifiers处理

CI框架下column注入

对$item做escape_identifiers处理与前面的$alias拼接返回

跟踪一下escape_identifiers

关键语句

preg_replace('/'.$preg_ec[0].'?([^'.$preg_ec[1].'\.]+)'.$preg_ec[1].'?(\.)?/i', $preg_ec[2].'$1'.$preg_ec[3].'$2', $item);

把item用重音符包裹,测试一下如下:($item 是 name%20from%20flash%20union%20select ,$alias是aa,前面只做了一次空格校验)

CI框架下column注入

这个`无法闭合,因为`会被正则检测出来并在其后加上`。

回到前面,发现

CI框架下column注入

strcspn函数,在 php 中进行匹配,只要检测到有相同部分,就停止检测,例如下面

CI框架下column注入

所以,只要我们包含(、)、'任意一个就可以在protect_identifiers处理前直接return......

CI框架下column注入

可以正确执行,但是因为最后显示结果使用的是

CI框架下column注入

所以不能拿到结果,要盲注,测试一下sleep()

CI框架下column注入

可以执行。

原题还过滤了大小于号,逗号等字符,所以最终的payload

http://ctf28.challenge10.0ops.net/fish/case%20when%20ascii(substr((1)from(1)for(1)))=49%20then%20sleep(2)%20else%201%20end%20from%20dual%20where%20(1=1)%20union%20select%20info%20/0.tctf


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

JavaScript Patterns

JavaScript Patterns

Stoyan Stefanov / O'Reilly Media, Inc. / 2010-09-21 / USD 29.99

What's the best approach for developing an application with JavaScript? This book helps you answer that question with numerous JavaScript coding patterns and best practices. If you're an experienced d......一起来看看 《JavaScript Patterns》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器