前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PHP代码审计笔记--XSS跨站脚本

PHP代码审计笔记--XSS跨站脚本

作者头像
Bypass
发布2019-07-08 17:50:14
8730
发布2019-07-08 17:50:14
举报
文章被收录于专栏:BypassBypass

0x01 最简单的XSS

输入即输出,漏洞代码示例:

代码语言:javascript
复制
<?php
    echo $_REQUEST[ 'id' ];
?>

测试语句:

代码语言:javascript
复制
?id=<script>alert(/xss/)</script>

安全建议:将输出到页面的参数转义为html实体编码。

0x02 编码解码

编码解码输出时,可能导致XSS编码绕过的情况。

漏洞代码示例:

代码语言:javascript
复制
<?php
$a=urldecode($_GET['id']); //接收参数并进行url解码
$b=htmlspecialchars($a);   //HTML ENCODE处理,到这里都是没有问题的
echo urldecode($b);        //最后,url解码输出
?>

测试语句:

代码语言:javascript
复制
id=%25253Cscript%25253Ealert(/xss/)%25253C/script%25253E 

这边代码逻辑中,问题根源在于最后一句的url解码输出,导致存在三重url编码绕过的情况。

根据实际情况,给出安全建议:HTML ENCODE处理后直接输出变量,无需再次url解码。

0x03 HTML不规范

HTML代码编写不规范,可能导致的问题,我们来看一个案例:

漏洞代码示例:

代码语言:javascript
复制
<?php
    $name = htmlspecialchars($_GET['name']);
?>
<input type='text' class='search' value='<?=$name?>'>

获取参数,在一个input元素的属性里输出这个变量,我们注意到这里使用的是单引号闭合,而函数默认只是转化双引号("), 不对单引号(')做转义。

因此,可以用单引号闭合,

测试语句:

代码语言:javascript
复制
?name=222' onclick='alert(/xxs/)

针对这种情况的修复安全建议:将HTML标签的属性值用双引号引起来。

0x04 黑名单过滤

通过在全局引入过滤函数,提供黑名单过滤,

漏洞代码示例:

代码语言:javascript
复制
<?php
    $name = htmlspecialchars($_GET['name']);
    $pregs = "/<script>|<\/script>|onclick|oncontextmenu|ondblclick|onmousedown|onmouseenter|onmouseleave|onmousemove|onmouseover|onmouseout|onmouseup|onkeydown|onkeypress|onkeyup/i";
    $check = preg_match($pregs, $name);
    if ($check) {
           echo 'not found';
           exit;
    }
?>
<input type='text' class='search' value='<?=$name?>'>

从html编写不规范,我们可以使用单引号闭合,然后去进一步构造触发事件,可是常见的XSS事件大多都被过滤了,

怎么快速地去找到可以拿来利用的XSS触发事件呢? 答:XSS FUZZ。

前提是要收集积累一些触发事件,利用自己编写python脚本进行fuzz:

虽然fuzz出来很多事件,但要结合具体输出位置去分析,找到合适的事件,最终得出Payload。

测试语句:

代码语言:javascript
复制
?name=111' onfocus='alert(/xss/)

0x05 漏洞防护

针对具体的漏洞情况去分析漏洞原因,给出修复建议,详见如上。常见的xss修复方法如下:

1、PHP提供了两个函数htmlentities()和htmlspecialchars() ,把一些预定义的字符转换为 HTML 实体。

防御代码示例:

代码语言:javascript
复制
<?php    
    echo htmlspecialchars($_REQUEST[ 'id' ]);
?>

2、其它的通用的补充性防御手段

代码语言:javascript
复制
1.在输出html时,加上Content Security Policy的Http Header
(作用:可以防止页面被XSS攻击时,嵌入第三方的脚本文件等)
(缺陷:IE或低版本的浏览器可能不支持)
2.在设置Cookie时,加上HttpOnly参数
(作用:可以防止页面被XSS攻击时,Cookie信息被盗取,可兼容至IE6)
(缺陷:网站本身的JS代码也无法操作Cookie,而且作用有限,只能保证Cookie的安全)
3.在开发API时,检验请求的Referer参数
(作用:可以在一定程度上防止CSRF攻击)
(缺陷:IE或低版本的浏览器中,Referer参数可以被伪造)

最后,附XSS FUZZ 脚本:

代码语言:javascript
复制
#! /usr/bin/env python
# _*_  coding:utf-8 _*_
import requests
import urlparse
import urllib
# 使用说明,修改字典即可使用,支持GET、POST等简单XSS验证
# url 支持 * 号  如  http://127.0.0.1/test.php?id=1*3333   payload会替换*号内容
global result_dict
result_dict={}
def get(url,para,payload):
  params={}
  result=urlparse.urlparse(url)
  params=urlparse.parse_qs(result.query,True) 
  if '*' in params[para][0]:
    params[para]=str(params[para][0]).replace("*", payload);
  else:
    params[para]=str(params[para][0])+payload
  m_url=result.scheme+"://"+result.netloc+result.path
  data = urllib.urlencode(params)
  geturl = m_url+'?'+data
  response = requests.get(geturl)
  result_dict[payload]=[response.content,len(response.content),response.status_code]
  return result_dict
def make_get_resule(url,para):
  with open('on.txt') as f:  
    for payload in f.xreadlines():
      payload =payload.strip()
      if '#' in payload or len(payload)==0:
        pass
      else:
        get(url,para,payload)
        result_analysis(payload)   
def post(url,data,para,payload):
  params={}
  params=urlparse.parse_qs(data,True) 
  if '*' in params[para][0]:
    params[para]=str(params[para][0]).replace("*", payload);
  else:
    params[para]=str(params[para][0])+payload
  response = requests.post(url,data=params,timeout=5)
  result_dict[payload]=[response.content,len(response.content),response.status_code]
  return result_dict
def make_post_resule(url,data,para):
  with open('on.txt') as f:  
    for payload in f.xreadlines():
      payload =payload.strip()
      if '#' in payload or len(payload)==0:
        pass
      else:
        post(url,data,para,payload)
        result_analysis(payload)
def result_analysis(payload):
  if  payload in result_dict[payload][0]:
    print "[+]" + payload +"  ok"
  if  result_dict[payload][0].count(payload)>1:
    print "[+]" + payload +"  repeat"
if __name__ == '__main__':
  result_dict={}
  methodselect = raw_input("[?] Select method: [G]ET or [P]OST (G/P): ").lower()
  if methodselect == 'g':
    url = raw_input("Please input url:")
    para = raw_input("Please input para:")
    if 'https://' in url:
      pass
    elif 'http://' in url:
      pass
    else:
      url = "http://"+url    
    make_get_resule(url,para)
  elif methodselect == 'p':
    url = raw_input("Please input url:")
    data = raw_input("Please input data:")
    para = raw_input("Please input para:")
    if 'https://' in url:
      pass
    elif 'http://' in url:
      pass
    else:
      url = "http://"+url
    make_post_resule(url,data,para)
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-04-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Bypass 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档