Skip to content

Commit

Permalink
表单完善,增加token的输出和校验功能,Core支持自动csrf攻击防御,默认配置完善
Browse files Browse the repository at this point in the history
  • Loading branch information
breath-co2 committed Oct 8, 2013
1 parent cc68651 commit 5323050
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 14 deletions.
72 changes: 71 additions & 1 deletion config.new.php
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@
* HTML5自动跨越请求支持
*
* 开启后,如果遇到AJAX跨越请求,则会自动加上 Access-Control-Allow-Origin 的支持
* 注意,只有支持HTML5的此协议的浏览器有用,IE6,7等浏览器这个
* 注意,只有支持HTML5的此协议的浏览器有用,IE6,7等浏览器不支持这个
*
* header("Access-Control-Allow-Origin: http://.../');
*
Expand Down Expand Up @@ -342,6 +342,76 @@






//---------------------------------------------------------- CSRF 相关

/**
* POST请求模式下自动检查引用页,如果是同主域名下的请求,将被通过,否则返回406错误
*
* 开启后将屏蔽所有非本域下URL的POST请求,建议开启,可有效避免 CSRF 攻击
*
* 自行检查的方法: `HttpIO::csrf_check()`, 若返回 `true` 则表示检查通过
*
* 若需要某个控制器关闭自动检查,在控制器里设置变量 `public $auto_check_post_method_referer = false` 即可,反之亦然,例如:
*
* class Controller_Test extends Controller
* {
* // 设置不自动验证
* public $auto_check_post_method_referer = false;
*
* public function action_default()
* {
* echo 'hi';
* }
* }
*
* @see http://baike.baidu.com/view/1609487.htm
* @see http://www.nxadmin.com/web/924.html
* @return boolean
*/
$config['auto_check_post_method_referer'] = true;


/**
* 在表单使用token时创建校验数据存放在服务器缓存中的时间,单位秒
*
* 设置成0表示禁用缓存保存校验数据,此时数据将随表单一起输出,为了提高安全请设置 `$config['form_token_hash_key']` 值
* 设置成0将不会对服务器造成缓存数据压力,但相对于把验证数据存在服务器安全性会差些,此时所以的校验将依赖 `$config['form_token_hash_key']`,并且存在在有效期内被重复利用的可能
*
* @var string
*/
$config['form_token_cache_time'] = 86400;


/**
* 表单使用token时存放随机key的缓存配置名,默认null即 `Cache::DEFAULT_CONFIG_NAME` 的值
*
* 在 `Form::open('uri', array(), true)` 和 `Form::check_token()` 时使用到
*
* @var string
*/
$config['form_token_cache_name'] = null;


/**
* 在表单使用token时创建hash值时用到的key
*
* @var string
*/
$config['form_token_hash_key'] = '';









// ----------------------------------------------------------- 服务器同步

/**
* 文件保存同步模式
*
Expand Down
17 changes: 16 additions & 1 deletion core/classes/core.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,14 @@ public static function config($key = null, $default = null)
{
$v = Core::$core_config;
}
else
elseif (isset(Core::$config[$cname]))
{
$v = Core::$config[$cname];
}
else
{
return $default;
}

if ($c)foreach ($c as $i)
{
Expand Down Expand Up @@ -619,6 +623,17 @@ public static function execute($uri)
}
unset($ispublicmethod);

# POST 方式,自动CSRF判断
if (HttpIO::METHOD=='POST')
{
$auto_check_post_method_referer = isset($controller->auto_check_post_method_referer)?$controller->auto_check_post_method_referer:Core::config('auto_check_post_method_referer', true);

if ($auto_check_post_method_referer && !HttpIO::csrf_check())
{
throw new Exception(__('Not Acceptable.'), 406);
}
}

if (isset($found['route']))
{
# 设置Route参数
Expand Down
164 changes: 152 additions & 12 deletions core/classes/form.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@
*/
class Core_Form
{

/**
* Generates an opening HTML form tag.
* 创建一个表单
*
* $add_token 参数为是否创建一个token验证隐藏表单,用于预防 CSRF 攻击
*
* !!! $add_token 功能适用于动态页面,而不能应用于有可能被缓存或HTML静态化的页面
*
* // Form will submit back to the current page using POST
* echo Form::open();
Expand All @@ -27,21 +30,28 @@ class Core_Form
*
* @param string form action, defaults to the current request URI
* @param array html attributes
* @param boolean $add_token 是否添加token验证功能
* @return string
* @uses HttpIO::instance
* @uses URL::site
* @uses Core::url
* @uses HTML::attributes
* @uses Text::random
* @uses Cache::set
* @uses Text::rc4_encrypt
* @uses Form::hidden
*/
public static function open($action = null, array $attributes = null)
public static function open($action = null, array $attributes = null, $add_token = true)
{
if (strpos($action, '://') === false)
if (null!==$action)
{
// Make the URI absolute
$action = Core::url($action);
}
if (false===strpos($action, '://'))
{
// Make the URI absolute
$action = Core::url($action);
}

// Add the form action to the attributes
$attributes['action'] = (string)$action;
// Add the form action to the attributes
$attributes['action'] = (string)$action;
}

// Only accept the default character set
$attributes['accept-charset'] = Core::$charset;
Expand All @@ -52,7 +62,17 @@ public static function open($action = null, array $attributes = null)
$attributes['method'] = 'post';
}

return '<form' . HTML::attributes($attributes) . '>';
$str_token = '';

if ($add_token)
{
foreach (Form::get_token() as $key => $value)
{
$str_token .= Form::hidden($key, $value);
}
}

return '<form' . HTML::attributes($attributes) . '>' . $str_token;
}

/**
Expand Down Expand Up @@ -421,5 +441,125 @@ public static function label($input, $text = null, array $attributes = null)
return '<label' . HTML::attributes($attributes) . '>' . $text . '</label>';
}

/**
* 获取一个token的数据数组
*
* @return array
*/
public static function get_token()
{
$key = '_form_token/' . date('Ymd') .'/'. Text::random('distinct', 16);
$value = Text::random(null, 32);

$cache_time = (int)Core::config('form_token_cache_time', 0);

$token = array();
if (!$cache_time>0)
{
# 将value加密后传入表单
$token['value'] = Text::rc4_encrypt($value, null, $cache_time);
}
else
{
# 将value存在缓存中
Cache::instance(Core::config('form_token_cache_name'))->set($key, $value, $cache_time);
}

$token['key'] = Text::rc4_encrypt($key);
$token['hash'] = Form::get_token_hash($value, $key);

return $token;
}

/**
* 校验表单token
*
* 当使用 `Form::open()` 方法开启 token 后,可试用此方法在接受页面中校验token是否正确
*
* @return bool
*/
public static function check_token()
{
if (HttpIO::METHOD=='GET')
{
if (!isset($_GET['__form_token__']))
{
return false;
}
}
else
{
if (!isset($_POST['__form_token__']))
{
return false;
}
}

if (!$_POST['__form_token__'] || !is_array($_POST['__form_token__']) || !isset($_POST['__form_token__']['key']) || !isset($_POST['__form_token__']['hash']))return false;

$cache_time = (int)Core::config('form_token_cache_time', 0);

$key = Text::rc4_decryption($_POST['__form_token__']['key']);
if (!$key || substr($key, 0, 12)!='_form_token/')
{
return false;
}

if (!$cache_time>0)
{
if (!isset($_POST['__form_token__']['value']))return false;

# 从表单中解密数据
$value = Text::rc4_decryption($_POST['__form_token__']['value']);
}
else
{
# 从缓存中获取
$value = Cache::instance(Core::config('form_token_cache_name'))->get($key);
}

if (!$value)return false;

if (Form::get_token_hash($value, $key)!=$_POST['__form_token__']['hash'])return false;

return true;
}


/**
* 删除相关token,避免被重复利用
*
* @return null
*/
public static function delete_token()
{
$cache_time = (int)Core::config('form_token_cache_time', 0);
if (!$cache_time>0)
{
return null;
}

if (!isset($_POST['__form_token__']['key']))return null;

$key = Text::rc4_decryption($_POST['__form_token__']['key']);
if (!$key || substr($key, 0, 12)!='_form_token/')
{
return null;
}

# 从缓存中获取
Cache::instance(Core::config('form_token_cache_name'))->delete($key);
}


/**
* 根据一个字符串生成一个token hash
*
* @param string $str
* @return string
*/
protected static function get_token_hash($str, $key)
{
return sha1('s$(2'. $str .'_'. $key .'$#&@dft24kwq' . Core::config('form_token_hash_key'));
}
}

0 comments on commit 5323050

Please sign in to comment.