NoSQLinjectionBasicsTHM
NoSQL injection Basics
在我们学习NoSQL注入之前,让我们先来看看MongoDB是什么以及它是如何工作的。
MongoDB
与MySQL、MariaDB或PostgresSQL非常相似,MongoDB是另一种数据库,您可以在其中以有序的方式存储数据。MongoDB允许您以快速和结构化的形式检索数据子集。如果你熟悉关系数据库,你可以假设MongoDB的工作方式与任何其他数据库类似。主要的例外是信息不是存储在表中,而是存储在文档中。
您可以将这些文档视为一个简单的字典结构,其中存储了键值对。在某种程度上,它们与传统关系数据库中的记录非常相似,但信息的存储方式不同。例如,假设我们正在为HR部门创建一个Web应用程序,并且我们希望存储基本的员工信息。然后,您将为每个员工创建一个文档,其中包含格式如下的数据:
{"_id" : ObjectId("5f077332de2cdf808d26cd74")"username" : "lphillips", "first_name" : "Logan", "last_name" : "Phillips", "age" : "65", "email" : "lphillips@example.com" } |
正如您所看到的,MongoDB中的文档存储在一个具有任意数量字段的关联数组中。
MongoDB允许您将具有类似功能的多个文档分组在称为集合的 更高层次结构中,以用于组织目的。集合相当于关系数据库中的表。继续我们的HR示例,所有员工的文档都可以方便地分组到一个名为“people”的集合中,如下图所示。
多个集合最终被分组到数据库中,这是MongoDB中最高层次的元素。在关系数据库中,数据库概念将表分组在一起。在MongoDB中,它对相关集合进行分组。
查询数据库
与任何数据库一样,一种特殊的语言用于从数据库中检索信息。就像关系数据库使用SQL的某些变体一样,非关系数据库(如MongoDB)使用NoSQL。一般来说,NoSQL是指查询非SQL数据库的任何方式,这意味着它可能会因所使用的数据库而异。
使用MongoDB,查询使用一个结构化的关联数组,其中包含要满足的标准组来过滤信息。这些过滤器提供了与SQL中的where子句类似的功能,并在需要时提供运算符来构建复杂的查询。
为了更好地理解NoSQL查询,让我们假设我们有一个数据库,其中包含以下三个文档的人的集合:
如果我们想构建一个过滤器,以便只检索last_name为“Sandler”的文档,我们的过滤器将如下所示:
['last_name' => 'Sandler'] |
因此,此查询仅检索第二个文档。
如果我们想过滤性别为男性,姓氏为菲利普斯的文档,我们将使用以下过滤器:
['gender' => 'male', 'last_name' => 'Phillips'] |
这将只返回第一个文档。
如果我们想检索年龄小于50岁的所有文档,我们可以使用以下过滤器:
['age' => ['$lt'=>'50']] |
这将返回第二个和第三个文档。注意,我们在嵌套数组中使用了**$lt**运算符。运算符通过嵌套条件允许更复杂的过滤器。可以在以下链接中找到可能的操作符的完整参考:
NoSQL注入
如何注入NoSQL
当查看如何构建NoSQL过滤器时,绕过它们注入任何有效负载可能看起来是不可能的,因为它们依赖于创建结构化数组。与SQL注入不同,查询通常通过简单的字符串串联构建,NoSQL查询需要嵌套的关联数组。从攻击者的角度来看,这意味着要注入NoSQL,必须能够将数组注入应用程序。
幸运的是,许多服务器端编程语言都允许通过在HTTP请求的查询字符串上使用特殊语法来传递数组变量。在本例中,让我们关注以下用PHP编写的简单登录页面代码:
Web应用程序使用“myapp“数据库和“login“集合对MongoDB进行查询,请求通过过滤器**['username'=>$user, 'password'=>$pass]**
的任何文档,其中**$user和$pass**都直接从HTTPPOST参数获得
如果我们以某种方式发送一个数组给**$user和$pass**变量,内容如下:
$user = ['$ne'=>'xxxx'] |
最终得到的过滤器看起来像这样:
['username'=>['$ne'=>'xxxx'], 'password'=>['$ne'=>'yyyy']] |
我们可以欺骗数据库返回任何用户名不等于’xxxx‘,密码不等于’yyyy‘的文档。这可能会返回登录集合中的所有文档。因此,应用程序将假设执行了正确的登录,并允许我们以与从数据库获得的第一个文档相对应的用户权限进入应用程序。
尚未解决的问题是如何将数组作为POSTHTTP请求的一部分进行传递。事实证明,PHP和许多其他语言允许您通过在POST请求体上使用以下符号来传递数组:
user[$ne]=xxxx&pass[$ne]=yyyy |
所以让我们启动我们最喜欢的代理并尝试测试它。在本指南中,我们将使用BurpProxy。
浏览登录屏幕
首先,让我们打开http://10.10.88.239/上的网站,并发送一个不正确的user/pass来捕获Burp上的请求:
原始捕获的登录请求如下所示:
我们现在继续拦截另一个登录请求,并修改user和pass变量以发送所需的数组:
这将强制数据库返回所有用户文档,因此我们最终登录到应用程序:
以其他用户身份登录
我们设法绕过了应用程序的登录屏幕,但是使用前一种技术,我们只能作为数据库返回的第一个用户登录。通过使用$nin操作符,我们将修改我们的有效负载,以便我们可以控制我们想要获得的用户。
首先,$nin操作符允许我们通过指定条件来创建一个过滤器,其中所需的文档有一些字段,而不是在值列表中。因此,如果我们想以除admin以外的任何用户身份登录,我们可以修改负载如下所示:
这将转换为具有以下结构的过滤器:
['username'=>['$nin'=>['admin'] ], 'password'=>['$ne'=>'aweasdf']] |
它告诉数据库返回用户名不是admin且密码不是aweasdf的任何用户。因此,我们现在被授权访问另一个用户的帐户。
注意,$nin运算符接收一个要忽略的值列表。我们可以通过调整负载来继续扩展列表,如下所示:
这将导致这样的过滤器:
['username'=>['$nin'=>['admin', 'jude'] ], 'password'=>['$ne'=>'aweasdf']] |
这可以根据需要重复多次,直到我们可以访问所有可用帐户。
提取用户密码
此时,我们可以访问应用程序中的所有帐户。但是,重要的是要尝试提取实际使用的密码,因为它们可能在其他服务中重复使用。为了实现这一点,我们将滥用$regex操作符向服务器询问一系列问题,使我们能够通过类似于玩游戏hangman的过程来恢复密码。
首先,让我们以之前发现的用户之一为例,尝试猜测他的密码长度。我们将使用以下payload来实现:
请注意,我们正在询问数据库是否有用户名为admin且密码与regex:**^.{7}$**
匹配的用户。这基本上表示一个长度为7的字符串。由于服务器响应一个登录错误,我们知道用户admin的密码长度不是7。经过反复试验,我们终于得出了正确的答案:
我们现在知道用户admin的密码长度为5。现在,为了计算实际内容,我们修改我们的有效负载如下:
我们现在正在使用长度为5的正则表达式(一个字母c加4个点),匹配发现的密码长度,并询问管理员的密码是否匹配正则表达式^c....$
,这意味着它以一个字母c开头,后跟任意4个字符。由于服务器响应是无效登录,我们现在知道密码的第一个字母不能是“c”。我们继续迭代所有可用的字符,直到我们从服务器获得成功的响应:
这确认了admin密码的第一个字母是“a”。可以对其他字母重复相同的过程,直到恢复完整的密码。如果需要,也可以为其他用户重复此操作。
反正就是用burp加载一个’0’-‘9’,’a’-‘z’的字典,然后爆就完事了,能密码复用的用户是pedro