我基于这个tutorial创建了一个简单的ajax表单,一切都很好用。我唯一的问题是我无法弄清楚如何验证URL字段的数据。似乎即使我将字段类型设置为URL,如果它不是URL,它仍会处理。
有任何想法吗?
example.html的
<html>
<head>
<script>
function ajax_post(){
// Create our XMLHttpRequest object
var hr = new XMLHttpRequest();
// Create some variables we need to send to our PHP file
var url = "my_parse_file.php";
var dlink = document.getElementById("dirtylink").value;
var vars = "dlink="+dlink;
hr.open("POST", url, true);
// Set content type header information for sending url encoded variables in the request
hr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// Access the onreadystatechange event for the XMLHttpRequest object
hr.onreadystatechange = function() {
if(hr.readyState == 4 && hr.status == 200) {
var return_data = hr.responseText;
document.getElementById("status").innerHTML = return_data;
}
}
// Send the data to PHP now... and wait for response to update the status div
hr.send(vars); // Actually execute the request
document.getElementById("status").innerHTML = "processing...";
}
</script>
</head>
<body>
<h2>Ajax Post to PHP and Get Return Data</h2>
<input id="dlink" name="dlink" class="putfield" type="url" pattern="https?://.+" required name="website">
<input name="myBtn" type="submit" value="Submit Data" onclick="ajax_post();"> <br><br>
<div id="status"></div>
</body>
</html>
my_parse_file.php
<?php
echo 'Thank you '. $_POST['firstname'] . ' ' . $_POST['lastname'] . ', says
the PHP file';
?>
这些标记不是有效/标准HTML标记:
像这样写你的html输入:
<input id="dlink" name="dlink" class="putfield" type="text" value="http://"/>
(你想要命名的dlink或网站!?)
然后你必须首先验证/控制服务器端的所有输入,所以在my_parse_file.php中:
<?php
//unescape data if magic quotes is activated
function strip(&$str) {
if(!is_array($str)) { $str = stripslashes($str); }
}
if(get_magic_quotes_gpc() || get_magic_quotes_runtime()) {
array_walk($_GET, 'strip');
array_walk($_POST, 'strip');
}
//init vars
if(isset($_POST['dlink'])) { $dlink = trim($_POST['dlink']); }else{ $dlink = ''; }
//tiny protect against code injection (XSS)
//maybe need to be revised, with eventual addslashes(), depanding on what you do with $dlink
$dlink = strip_tags($dlink);
//protect against multiline injection
if(preg_match('`^([^\r\n]*)`', $dlink, $match)) { $dlink = $match[1]; }
//control is a right url, can need a little improvement for the right domain format
if(!preg_match('`^(http[s]?://.+)`i', $dlink)) { echo "error"; exit(); }
echo 'Thank you! the url is '.$dlink.', says the PHP file';
?>
然后你可以在客户端添加JS控件,以便更好地响应并避免http请求,如果坏dlink:
var dlink = document.getElementById("dlink").value;
if(!dlink.match(/^http[s]?:\/\/.+/gi)) { alert("url not valid"); return 0; }
var vars = "dlink="+dlink;
请注意不安全的直接回显$ _POST ['varname']。
看起来filter_var()也能控制网址,就像西尔维奥所说的那样。
在我的回答旁边,我想手工做一些filter_var()的替代方法,并寻找最简单的方法来保护$ _POST和$ _GET“echo”反对注入。
我不太认可filter_var()如何验证/清理网址,因为为什么我要在我的数据库中记录/选择包含注入的网址,如“domain.com<script>alert(cookie)</script>
”,或甚至显示给客户端“this url is domain.comalert(cookie)
”。
所以这就是我所做的:
function safe_char($str) {
$buf = '';
$enable = array(
9 => 1,//\t
10 => 1,//\n
13 => 1//\r
);
$len = mb_strlen($str);
$i = 0;
while($i < $len) {
$ascii = ord($str[$i]);
//remove unwelcome char, about decimal 0-31 and 127, keep only \t \r \n
if($ascii !== 127/*DEL*/ && ($ascii > 31 || isset($enable[$ascii]))) {
$buf .= $str[$i];
}
$i++;
}
return $buf;
}
function safe_strip_tags($str, $remove_hack=false, $log_hack=false) {
if($remove_hack) {
//$str_ini = $str;
//remove tag content only when tags script/noscript detected
$str = preg_replace('`<[[:space:]]*(script|noscript)[^>]*>(.*?<[[:space:]]*/\1[[:space:]]*>|.+)`is', '', $str);
//logs hack
//if($log_hack && $str !== $str_ini) {
// logs(array('try injection', $str_ini));
// }
}
//safe delete tags
$str = strip_tags($str);
//delete the last unique > or <
$str = preg_replace('`[<>]+`s', '', $str);
return $str;
}
function safe_write($str) {
//replace by the html entities the critical char that cause injection works
$char = array('&', '<', '>', '"', '\'');
$replace = array('&', '<', '>', '"', ''');
return str_replace($char, $replace, $str);
}
function filter_url($str, &$url=false, $strict=false) {
$err = true;
$str = trim($str);
//remove unwelcome control char (about from x00 to x1F), it keep only \t \r \n
$str = safe_char($str);
//remove html tag and protect against injection (XSS)
$url = safe_strip_tags($str, true, true);
//protect against multiline injection
if(preg_match('`^([^\r\n]*)`', $url, $match)) { $url = $match[1]; }
//test is like an url
if(!preg_match('`^(http|ftp)[s]?://.+`i', $url)) {
//and reject other scheme
if($url !== '' && mb_strpos($url, '://') === false) {
//maybe case "www.url.com" so try add an http scheme
$url = 'http://'.$url;
$err = false;
}
}
else{ $err = false; }
//going to confirm url have valid domain
if(!$err) {
//remove char that we dont want in an url
$url = preg_replace('`[\t]+`', '', $url);
$host = parse_url($url, PHP_URL_HOST);
if($host != null) {
//no special char in domain name
if(!preg_match('`^[a-z0-9._-]+$`i', $host)) { $err = true; }
//no double dot in domain name
if(!$err && mb_strpos($host, '..') !== false) { $err = true; }
//domain name
if(!$err && !preg_match('`[a-z0-9_-]{1,63}\.[a-z.]{2,10}$`i', $host)) { $err = true; }
//local dev for http://localhost
//if($err && preg_match('`^[a-z0-9_-]{1,63}$`i', $host)) { $err = false; }
//more strict controls
if(!$err) {
$xpl = explode('.', $host);
foreach($xpl as $v) {
//label not more long than 63 char
if(mb_strlen($v) > 63) { $err = true; break; }
//label must start with a letter
//if(preg_match('`^[0-9]+`', $v)) { $err = true; break; }
//label with underscore is normally not valid
//if(mb_strpos($v, '_') !== false) { $err = true; break; }
}
}
//ip
if($err && preg_match('`^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$`', $host)) { $err = false; }
//its enough, and not so restricted for the future, if you really want to ctrl an url, you have to request it
}
//bad host
else{ $err = true; }
}
//url have been modified
if($strict && $str !== $url) {
$err = true;
}
if($err) { $url = false; }
else{ return true; }
return false;
}
function filter_string($str, &$string=false, $strict=false) {
$str = trim($str);
$string = safe_char($str);//filter_var() cannot do that, so no php_filter_string()
//string have been modified
if($strict && $str !== $string) {
return false;
}
return true;
}
如果您发现注射/错误或优化它,请与我们分享......
测试自制的filter_url()结果:
//i writted the result from filter_url() in each comment
$arr = array(
'https://url.com',//https://url.com
'http://url.com',//http://url.com
'http://url.com/test',//http://url.com/test
'http://url.com/test.php?param=a\'b \"c*&plus=1',//http://url.com/test.php?param=a'b \"c*&plus=1
'http://url.com/\'t"e*s t',//http://url.com/'t"e*s t
'http://urlcom',//FALSE
'http://urlcom/url.com',//FALSE
'http://url.com\test',//FALSE
'http://url.com\'"*',//FALSE
'http://url.c\'"*',//FALSE
'http://url.\'"*',//FALSE
'',//FALSE
'u',//FALSE
'u.co',//http://u.co
'http://',//FALSE
'http://u',//FALSE
'http://u.c',//FALSE
'http://u.co',//http://u.co
'http://ur.co',//http://ur.co
'http://www.url.com',//http://www.url.com
'http://www.url',//http://www.url
'http://url_url.com',//http://url_url.com
'http://www.thislabelistoolongthislabelistoolongthislabelistoolongthislabelistoolong.com',//FALSE
'http://localhost',//FALSE
'http://4url.com',//http://4url.com
'http://sub.sub.url.com',//http://sub.sub.url.com
'http://l.s.s.url.com',//http://l.s.s.url.com
'http://127.0.0.1',//http://127.0.0.1
'http://127.0.0.1.2',//FALSE
'http://127.0.0',//FALSE
'http://127.0.0.1/filter/',//http://127.0.0.1/filter/
'http://127.0.0.url',//http://127.0.0.url
'http://127.url',//http://127.url
'http://url.127',//FALSE
'http://u27.c27',//FALSE
'http://u27.com',//http://u27.com
'http://127.0.0.1:80/filter/',//http://127.0.0.1:80/filter/
'http://127.0.0.1.2:80/filter/',//FALSE
'http://1278.0.0.1.2:80/filter/',//FALSE
'ftps://127.0.0.1:80/filter/',//ftps://127.0.0.1:80/filter/
'ftp://url.com',//ftp://url.com
'javascript://comment%0Aalert(1)',//FALSE
'javascript://url.com',//FALSE
'www.url.com',//http://www.url.com
'http://url..com',//FALSE
'http://url.com..com',//FALSE
'http://url.com/te..st',//http://url.com/te..st
'http://url.com/test?param=%0D%0A%61%62',//http://url.com/test?param=%0D%0A%61%62
'http://url.com/'."\r\n".'multiline',//http://url.com/
'http://url.com/'."\n".'multiline',//http://url.com/
'http://url.com/x<i; j>y; >>',//http://url.com/xy;
'http://url.com/<tag<one>two>text',//http://url.com/text
'http://url.com/<tag<one>two>text<three>',//http://url.com/text
'http://url.com/"><script>alert(cookie)</script>',//http://url.com/"
'http://url.com/%0D%0A<script>alert(cookie)</script>',//http://url.com/%0D%0A
'http://url.com/%0D%0A<script>alert(cookie)path/',//http://url.com/%0D%0A
'http://url.com/<strong onload="alert(cookie)">txt</strong>',//http://url.com/txt
'http://url.com/< space >text',//http://url.com/spacetext
'http://url.com/<xxx script yyy>txt</script>',//http://url.com/txt
'http://url.com/%3Ctag%3Cone%3Etwo%3Etext%3Cthree%3E',//http://url.com/%3Ctag%3Cone%3Etwo%3Etext%3Cthree%3E
'http://url.com/%0D%0A%3Cscript%3Ealert(cookie)%3C/script%3E',//http://url.com/%0D%0A%3Cscript%3Ealert(cookie)%3C/script%3E
'http://url.com/canbenice%0D%0A%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%28%63%6F%6F%6B%69%65%29%3C%2F%73%63%72%69%70%74%3E',//http://url.com/canbenice%0D%0A%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%28%63%6F%6F%6B%69%65%29%3C%2F%73%63%72%69%70%74%3E
//'http://url.co�m/charctrl',//http://url.com/charctrl
);
$charctrl = '';
$i = 0;
while($i < 32) {
if($i!==9 && $i!==10 && $i!==13) {
$charctrl .= chr($i);
}
$i++;
}
$charctrl .= chr(127);
$arr[] = 'http://url.co'.$charctrl.'m/charctrl';
echo '<pre>';
foreach($arr as $v) {
echo $v.' => ';
if(filter_url($v, $url)) { echo $url; }else{ echo 'FALSE'; }
echo "\r\n";
}
echo '</pre>';
echo "\r\n\r\n".'<br/><br/>'."\r\n\r\n";
echo '<pre>';
foreach($arr as $v) {
echo $v.' => ';
if(php_filter_url($v, $url)) { echo $url; }else{ echo 'FALSE'; }
echo "\r\n";
}
echo '</pre>';
filter_var()解决方案:很抱歉我不知道这些filter_var()函数......这个函数有一个不明确的名字,但是如果使用得当它最终是安全的,所以要小心选择正确的id / flag。
function php_filter_url($str, &$url=false, $strict=false) {
$err = true;
$str = trim($str);
$url = $str;
//protect against multiline injection
if(preg_match('`^([^\r\n]*)`', $url, $match)) { $url = $match[1]; }
//add this because FILTER_VALIDATE_URL accept others scheme
if(!preg_match('`^(http|ftp)[s]?://.+`i', $url)) {
//reject other scheme
if($url !== '' && mb_strpos($url, '://') === false) {
//maybe case "www.url.com" so try add an http scheme
$url = 'http://'.$url;
$err = false;
}
}
else{ $err = false; }
if(!$err) {
$url = filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED);
if(!$url) { $err = true; }
}
if(!$err) {
$url = filter_var($url, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES);
if(!$url) { $err = true; }
}
//url have been modified
if($strict && $str !== $url) {
$err = true;
}
if($err) { $url = false; }
else{ return true; }
return false;
}
我提到我很抱歉我的“异国情调”缩进,我不喜欢所有的“官方”,试图回去,但不可能......所以我能理解你对我的想法:D
测试filter_var()结果,别名php_filter_url():我让你自己尝试网址测试,有一些“假”匹配,但它看起来不是那么糟糕,除了这些结果:
Bench:filter_url()比php_filter_url()慢大约5倍,我们可以在不丢失简单可读脚本的情况下进行优化。但它不是戏剧性的替补。 (PHP 5.4)
最佳解决方案:如果您需要处理filter_var()不能处理的情况,请使用自制解决方案。最后,一个网址,即使写得很好,也经过验证,可能是一个糟糕的网址......你必须要求它真正知道。当有人尝试注入某些东西时,我怀疑是否有真实的信息,所以这些“url.comalert(cookie)”最终无用,自制版本尝试将其清理为自由空间,并且可以将注入信息记录到日志中。嗯,我只是想,也许我们不得不验证检测到注射的变量...
关于表单:由于javascript(客户端)验证输入不安全的原因,你必须处理php返回的最终错误,以确保在javascript中做什么。
您的代码示例不能很好地适应这种情况,因为您通常需要使用xml响应而不是实际的文本响应,以正确验证“在xhtml的艺术中”javascript中的信息。 (它需要更多的代码/理解)
所以,为了保持简单,但也非常正确,你可以选择像这样的替代品:
<html>
<head>
<style type="text/css">
.field {
font-weight:bolder;
border:2px gray solid;
color:black;
}
.fieldError {
border:2px red solid;
color:red;
}
</style>
<script type="text/javascript">
function getXhr() {
var xhr = false;
try{
xhr = new XMLHttpRequest();
}catch (e){
try{
xhr = new XDomainRequest();
}catch (e){
try{
xhr = new ActiveXObject('Msxml2.XMLHTTP');
}catch (e){
try{
xhr = new ActiveXObject('Microsoft.XMLHTTP');
}catch (e){
alert('Your browser is not compatible with XML request');
}
}
}
}
return xhr;
}
function encodeUrl(str) {
if(encodeURIComponent) { str = encodeURIComponent(str); }
else if(escape) { str = escape(str); }
//sure not any = and &
str = str.replace(/=/gi, "%3D");
str = str.replace(/&/gi, "%26");
return str;
}
function getNodeText(tag, content) {
var regex = new RegExp('<'+tag+'>(.*?)</'+tag+'>', 'g');
var match = regex.exec(content);
return match[1];
}
function safeWrite(str) {
str = str.replace(/</g, '<');
str = str.replace(/>/g, '>');
str = str.replace(/\"/g, '"');
str = str.replace(/\'/g, ''');
return str;
}
function ajaxPost() {
var err = false;
var errMsg = 'Invalid form';
//init obj
var dlink = document.getElementById("dlink");
var firstname = document.getElementById("firstname");
//reinit input class
dlink.className = "field";
firstname.className = "field";
//test input dlink
if(!dlink.value.match(/^http[s]?:\/\/.+/gi)) {
dlink.className = "field fieldError";
err = true;
}
//test input firstname
if(firstname.value == '') {
firstname.className = "field fieldError";
err = true;
}
//return directly on error
if(err) {
document.getElementById("status").innerHTML = errMsg;
return false;
}
//create our XMLHttpRequest object
var xhr = getXhr();
//create some variables we need to send to our PHP file
var url = "my_parse_file.php";
var param = "dlink="+encodeUrl(dlink.value)+"&firstname="+encodeUrl(firstname.value);
xhr.open("POST", url, true);
//set content type header information for sending url encoded variables in the request
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
//access the onreadystatechange event for the XMLHttpRequest object
xhr.onreadystatechange = function() {
if(xhr.readyState == 4 && xhr.status == 200) {
//alert(xhr.responseText);
//make our own tiny parser, and get all the response infos
var dlinkText = getNodeText("dlink", xhr.responseText);
var dlinkErr = getNodeText("dlinkErr", xhr.responseText);
var firstnameText = getNodeText("firstname", xhr.responseText);
var firstnameErr = getNodeText("firstnameErr", xhr.responseText);
//update var for a more secure/easy int type handle
dlinkErr = parseInt(dlinkErr, 10);
firstnameErr = parseInt(firstnameErr, 10);
//handle the real error returned by php
if(dlinkErr !== 0) {
dlink.className = "field fieldError";
err = true;
}
if(firstnameErr !== 0) {
firstname.className = "field fieldError";
err = true;
}
//form fail
if(err) { document.getElementById("status").innerHTML = errMsg; }
//form pass all the test, we recontrol with safeWrite() that there is no code injection
else{
var success = 'Thank you '+safeWrite(firstnameText)+'! the url is '+safeWrite(dlinkText);
document.getElementById("status").innerHTML = success;
}
}
}
//send the data to PHP now... and wait for response to update the status div
xhr.send(param);//actually execute the request
document.getElementById("status").innerHTML = "processing...";
}
</script>
</head>
<body>
<h2>Ajax Post to PHP and Get Return Data</h2>
<label for="dlink">dlink :</label><input id="dlink" name="dlink" type="text" value="http://" class="field"/><br/>
<label for="firstname">firstname :</label><input id="firstname" name="firstname" type="text" value="" class="field"/><br/>
<input id="submit" name="submit" type="submit" value="Submit Data" onmouseup="ajaxPost();">
<div id="status"></div>
</body>
</html>
my_parse_file.php
<?php
//force to refresh the cache of the browser
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
//include our filter functions
include './filter.php';
//init vars
$output = '';
$dlink_err = 0;
$firstname_err = 0;
//control $_POST
if(!isset($_POST['dlink'])) { $_POST['dlink'] = ''; $dlink_err = 1; }//trim is done inside filter functions
if(!isset($_POST['firstname'])) { $_POST['firstname'] = ''; $firstname_err = 1; }
//control/validate dlink is like a valid url, and clean the code injection try
if(!filter_url($_POST['dlink'], $dlink)) { $dlink_err = 1; }
//if(!php_filter_url($_POST['dlink'], $dlink)) { $dlink_err = 1; }//not well cleaned
//control firstname, only remove unwelcome charaters from the string, it can return false only if you use the $strict arg
if(!filter_string($_POST['firstname'], $firstname)) { $firstname_err = 1; }
//validate firstname is not empty
if($firstname === '') { $firstname_err = 1; }
//prepare the response, and protect against injection (XSS) with the help of safe_write()
$output .= '<dlink>'.safe_write($dlink).'</dlink>
<dlinkErr>'.$dlink_err.'</dlinkErr>
<firstname>'.safe_write($firstname).'</firstname>
<firstnameErr>'.$firstname_err.'</firstnameErr>';
echo $output;
?>
我关于“无效/不标准html标签”的第一个注意事项有点不清楚,它们是2015年以来HTML5的有效新标签属性,但是如果您使用它,您的网站将与不支持的“旧”客户端不兼容这个“新”HTML5。因此,要创建一个确实与世界兼容的网站,您必须使用HTML4,更准确地说是2000年以来的XHTML 1.0。
最后一件事在这段代码中并不好,如果javascript被取消激活,则表单不是函数。通常,创建网站的正确方法是让它在没有javascript的情况下工作(至少是主要的前端功能),然后才添加javascript图层。
所以,在我看来,你已经采取相反的方式,所以我建议你重新启动,首先创建简单的PHP表单+验证,然后才添加js层。
避免写入双重验证的提示是在两个地方重用相同的php文件验证,在简单的php表单的标题中第1个,在ajax请求中重复第2个(所以这个验证文件在我们的示例中是my_parse_file.php,但是它需要修改以处理谁发布)。我没有写这个解决方案,因为它没有真正回答使用ajax的问题,我们已经处于边界......:D
SOa瓶中的消息:“兼容的万维网”在这些时代就像是认真的,所以请大家,让它在创建兼容的网站后继续生存:)
你可以使用filter_var
方法和FILTER_VALIDATE_URL
标志,如下所示:
var_dump(filter_var($_POST['dlink'], FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED));
可选的FILTER_FLAG_SCHEME_REQUIRED
标志用于使用http / https进行验证输入
关于你的代码的另一个注意事项:url输入字段中有2个属性name
。
因此,您可以使用$dlink
方法设置filter_input
var,如下所示:
$dlink = filter_input(INPUT_POST, 'dlink', FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED);