MongoDB注入:如何攻击MongoDB?

来源:互联网 发布:dw软件免费吗 编辑:程序博客网 时间:2024/06/06 02:01

不管是商业项目还是个人项目,MongoDB都是一个非常好的数据库引擎,国内很多公司也开始用MongoDB。比起传统的数据库,这款数据库比较新,也有很多安全问题是大家还没有意识到的,而这些问题通常可以打得你措手不及。

本篇文章主要向大家介绍我在使用MongoDB的过程中遇到的问题,以及它是如何被用来修改数据库记录的。当然,利用过程很简单,不过其实各种方式的SQL注入技术说破了也就那么回事,但是依然有很多人容易犯这样的错误。

在我们开始前,我想先介绍下关于以下要用到的MongoDB的特性。MongoDB提供的更新机制是先定位到该文档,然后进行更新,如下例子:

{  name:"John",  info:{      age:65  }}

如上面的记录,你可以通过以下语句对它进行更新:

db.people.update({"name":"John"}, {"$set":{"info.age":66}})

是不是很酷炫,好吧,知道大家早就懂了 

但是,如果子键不是硬编码的,又该如何呢?我们该如何通过变量将内容传进去呢?如下:

keyName = request.form|'keyName'|keyData = request.form|'value'|db.people.update({"name":"John"}, {"$set":{"info.{}".format(keyName):keyData}})

后台程序从前端请求中获取到key和value的值以后,通过参数传入MongoDB的更新函数中。那么问题来了,如果前端输入的是一个恶意的参数呢。

以下是我在处理一个未知用户输入时候产生的问题,为了说明,接下来我们写一段用来展示这个漏洞。代码如下:

from flask import *import pymongoimport bsonimport uuid db = pymongo.MongoClient("localhost", 27017).test form = """<html><head></head><body><form method="POST"><input type="text" name="username" placeholder="Username"><input type="text" name="password" placeholder="Password">                                                                                                                                           <input type="text" name="firstname" placeholder="Firstname"><input type="text" name="lastname" placeholder="Lastname"/><input type="text" name="age" placeholder="Age"><input type="submit" value="Submit"></form></body></html>    """  app = Flask(__name__)app.secret_key = "secret" @app.route("/logout/")def logout():    session.pop("_id")    return redirect("/login/") @app.route("/")def index():    if "_id" not in session:        return redirect("/login/")    name = request.args.get("name")    lastname = request.args.get("lastname")    if not name:        return "<h1>Search for someone</h1><form method='GET'><input name='name' type='text' placeholder='First Name'><input name='lastname' type='text' placeholder='Last Name'><input type='submit'></form>"    else:        search_results = db.members.find_one({"{}".format(name):lastname})        if search_results:            search_results = name + " " + lastname + " is " + search_results['account_info']['age'] + " years old."        return "{}<form><input name='name' type='text' placeholder='First Name'><input name='lastname' type='text' placeholder='Last Name'><input type='submit'></form>".format(search_results) @app.route("/login/", methods=['GET', 'POST'])def login():    if request.method == "POST":        username = request.form['username']        password = request.form['password']        check = db.members.find_one({"username":username, "password":password})        if check:            session['_id'] = str(check)            return rediirect("/?name={}".format)        else:            return "Invalid Login"    return "<h1>Login</h1>" + form  @app.route("/signup/", methods=['GET', 'POST'])def signup():    if request.method == "POST":        username = request.form['username']        firstname = request.form['firstname']        lastname = request.form['lastname']        password = request.form['password']        age = request.form['age']        session['_id'] = str(db.members.insert({"username":username, "password":password, firstname:lastname, "account_info":{"age":age, "age":age, "isAdmin":False, "secret_key":uuid.uuid4().hex}}))        return redirect("/")    return "<h1>Signup</h1>" + form @app.route("/settings/", methods=['GET', "POST"])def settings():    if request.method == "POST":        username = request.form['username']        firstname = request.form['firstname']        lastname = request.form['lastname']        password = request.form['password']        age = request.form['age']        db.members.update({"_id":bson.ObjectId(session['_id'])}, {"$set":{"{}".format(firstname):lastname, "account_info.age":age, "username":username}})        return "Values have been updated!"    return "<h1>Settings</h1>" + form @app.route("/admin/", methods=['GET', 'POST'])def admin():    if "_id" not in session:        return redirect("/login/")    theUser = db.members.find_one({"_id":bson.ObjectId(session['_id'])})    if not theUser['account_info']['isAdmin']:        return "You do not have access to this page."    if request.method == "POST":        secret = request.form['secret_key']        return str(db.members.find_one({"account_info.secret_key":secret}))    return """<h1>Search user by secret key</h1>    <form method="post"><input type="text" name="secret_key" placeholder="Secret Key"/><input type="submit" value="Serach"/></form>    """ app.run(debug=True)

这个网站很简单。就是一个登陆页面,一个注册页面,一个设置页面,和一个index页面,用户可以在这些页面上输入他/她们的姓名,然后返回年龄,如下图。

需要注意的是,这段代码是很容易受到注入攻击的,接下来,我们来看看是如何进行注入的。

我们的目标是获得访问admin页面的权限。从网站代码中我们可以找到,后台是根据isAdmin字段来验证用户权限的,如下图

看一下后台的数据库大概是这样的:

其中,Firstname:Lastname是直接插入姓名的,看着很奇怪。

我们先创建一个用户,然后访问下/admin/页面,返回如下:

很好……果然没有权限访问。回顾下isAdmin可以用来控制该页面,也就是说,该用户在数据库中可能是这样的:

其中,firstname:lastname这一条是我们可控的,通过settings页面输入进去的,”username”, “password”, “firstname”, 和”lastname”实际上都是我们可以输入的,firstname:lastname在查询的时候是可以搞的,看起来似乎可以搞些文章。

把fistname改成account_info.isAdmin 并且把lastname改成”1 ”,1在python中代表的就是True。

点击Submint,发现修改成功了。

访问/admin/页面:

可以访问admin页面了,:D

同样的,要想用secret key查整个内容的话,可以这么做:

输入查询:

成功:

到此处,事实上我们能做的还有很多很多。我们可以用它来修改其他用户的账号密码,并且查看其他用户。在这里不多介绍。

很显然,这个网站的所有安全措施都没用了,敏感数据也变得危险。当然,当你的网站被攻击后,回过头来看代码,也许你会觉得自己的代码很搞笑,但这是绝对不容忽视的。好吧,其实我也犯过这样的错误,还好及时发现。

和关系型数据库的SQL注入一样,我们要做的就是过滤传入的参数。

好了,就酱紫啦~

欢迎加入我的QQ交流群425783133

0 0
原创粉丝点击