UDN-企业互联网技术人气社区

板块导航

浏览  : 562
回复  : 1

[MongoDB] 技术分享:如何Hacking MongoDB?

[复制链接]
我爱吃咸菜的头像 楼主
发表于 2016-4-25 11:12:14 | 显示全部楼层 |阅读模式
  来源:极客与黑客 东二门陈冠希



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

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

  在我们开始前,我想先介绍下关于以下要用到的MongoDB的特性。MongoDB提供的更新机制是先定位到该文档,然后进行更新,如下例子:
  1.  {
  2.   name:"John",
  3.   info:{
  4.       age:65
  5.   }
  6. }
复制代码

  如上面的记录,你可以通过以下语句对它进行更新:
  1.  db.people.update({"name":"John"}, {"$set":{"info.age":66}})
复制代码

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

  但是,如果子键不是硬编码的,又该如何呢?我们该如何通过变量将内容传进去呢?如下:
  1. keyName = request.form|'keyName'|
  2. keyData = request.form|'value'|
  3. db.people.update({"name":"John"}, {"$set":{"info.{}".format(keyName):keyData}})
复制代码

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

  以下是我在处理一个未知用户输入时候产生的问题,为了说明,接下来我们写一段用来展示这个漏洞。代码如下:
  1. from flask import *
  2. import pymongo
  3. import bson
  4. import uuid

  5. db = pymongo.MongoClient("localhost", 27017).test

  6. form = """
  7. <html><head></head><body>
  8. <form method="POST">
  9. <input type="text" name="username" placeholder="Username">
  10. <input type="text" name="password" placeholder="Password">                                                                                                                                          
  11. <input type="text" name="firstname" placeholder="Firstname">
  12. <input type="text" name="lastname" placeholder="Lastname"/>
  13. <input type="text" name="age" placeholder="Age">
  14. <input type="submit" value="Submit">
  15. </form></body></html>
  16.     """


  17. app = Flask(__name__)
  18. app.secret_key = "secret"

  19. @app.route("/logout/")
  20. def logout():
  21.     session.pop("_id")
  22.     return redirect("/login/")

  23. @app.route("/")
  24. def index():
  25.     if "_id" not in session:
  26.         return redirect("/login/")
  27.     name = request.args.get("name")
  28.     lastname = request.args.get("lastname")
  29.     if not name:
  30.         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>"
  31.     else:
  32.         search_results = db.members.find_one({"{}".format(name):lastname})
  33.         if search_results:
  34.             search_results = name + " " + lastname + " is " + search_results['account_info']['age'] + " years old."
  35.         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)

  36. @app.route("/login/", methods=['GET', 'POST'])
  37. def login():
  38.     if request.method == "POST":
  39.         username = request.form['username']
  40.         password = request.form['password']
  41.         check = db.members.find_one({"username":username, "password":password})
  42.         if check:
  43.             session['_id'] = str(check)
  44.             return rediirect("/?name={}".format)
  45.         else:
  46.             return "Invalid Login"
  47.     return "<h1>Login</h1>" + form

  48. @app.route("/signup/", methods=['GET', 'POST'])
  49. def signup():
  50.     if request.method == "POST":
  51.         username = request.form['username']
  52.         firstname = request.form['firstname']
  53.         lastname = request.form['lastname']
  54.         password = request.form['password']
  55.         age = request.form['age']
  56.         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}}))
  57.         return redirect("/")
  58.     return "<h1>Signup</h1>" + form

  59. @app.route("/settings/", methods=['GET', "POST"])
  60. def settings():
  61.     if request.method == "POST":
  62.         username = request.form['username']
  63.         firstname = request.form['firstname']
  64.         lastname = request.form['lastname']
  65.         password = request.form['password']
  66.         age = request.form['age']
  67.         db.members.update({"_id":bson.ObjectId(session['_id'])}, {"$set":{"{}".format(firstname):lastname, "account_info.age":age, "username":username}})
  68.         return "Values have been updated!"
  69.     return "<h1>Settings</h1>" + form

  70. @app.route("/admin/", methods=['GET', 'POST'])
  71. def admin():
  72.     if "_id" not in session:
  73.         return redirect("/login/")
  74.     theUser = db.members.find_one({"_id":bson.ObjectId(session['_id'])})
  75.     if not theUser['account_info']['isAdmin']:
  76.         return "You do not have access to this page."
  77.     if request.method == "POST":
  78.         secret = request.form['secret_key']
  79.         return str(db.members.find_one({"account_info.secret_key":secret}))
  80.     return """<h1>Search user by secret key</h1>
  81.     <form method="post"><input type="text" name="secret_key" placeholder="Secret Key"/><input type="submit" value="Serach"/></form>
  82.     """

  83. app.run(debug=True)
复制代码

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

14605195296020.jpg


14605195334181.jpg


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

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

14605195418209.jpg


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

14605195467788.jpg


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

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

14605195526076.jpg


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

14605195596689.jpg


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

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

14605195641876.jpg


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

14605195694973.jpg


  访问/admin/页面:

  可以访问admin页面了,:D

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

14605195852887.jpg


  输入查询:

14605195902457.jpg


  成功:

14605195951173.jpg


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

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

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

  好了,就酱紫啦~
14606190074637.jpg

相关帖子

发表于 2016-4-25 13:51:03 | 显示全部楼层
赞一个
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部