mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-01-31 12:49:34 +00:00
FHEMWEB: csrfToken added
git-svn-id: https://svn.fhem.de/fhem/trunk@6388 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
647d23f41c
commit
dd4da9d6ea
@ -6,6 +6,7 @@ use strict;
|
||||
use warnings;
|
||||
use TcpServerUtils;
|
||||
use HttpUtils;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
|
||||
#########################
|
||||
# Forward declaration
|
||||
@ -54,6 +55,7 @@ use vars qw($MW_dir); # moddir (./FHEM), needed by edit Files in new
|
||||
# structure
|
||||
|
||||
use vars qw($FW_ME); # webname (default is fhem), used by 97_GROUP/weblink
|
||||
use vars qw($FW_CSRF); # CSRF Token or empty
|
||||
use vars qw($FW_ss); # is smallscreen, needed by 97_GROUP/95_VIEW
|
||||
use vars qw($FW_tp); # is touchpad (iPad / etc)
|
||||
use vars qw($FW_sp); # stylesheetPrefix
|
||||
@ -128,6 +130,7 @@ FHEMWEB_Initialize($)
|
||||
JavaScripts
|
||||
SVGcache:1,0
|
||||
addStateEvent
|
||||
csrfToken
|
||||
alarmTimeout
|
||||
allowedCommands
|
||||
allowfrom
|
||||
@ -434,6 +437,8 @@ FW_answerCall($)
|
||||
$FW_RET = "";
|
||||
$FW_RETTYPE = "text/html; charset=$FW_encoding";
|
||||
$FW_ME = "/" . AttrVal($FW_wname, "webname", "fhem");
|
||||
$FW_CSRF = ($defs{$FW_wname}{CSRFTOKEN} ?
|
||||
"&fwcsrf=".$defs{$FW_wname}{CSRFTOKEN} : "");
|
||||
|
||||
$MW_dir = "$attr{global}{modpath}/FHEM";
|
||||
$FW_sp = AttrVal($FW_wname, "stylesheetPrefix", "");
|
||||
@ -499,7 +504,14 @@ FW_answerCall($)
|
||||
$FW_plotsize = AttrVal($FW_wname, "plotsize", $FW_ss ? "480,160" :
|
||||
$FW_tp ? "640,160" : "800,160");
|
||||
my ($cmd, $cmddev) = FW_digestCgi($arg);
|
||||
|
||||
if($cmd && $FW_CSRF) {
|
||||
my $supplied = $FW_webArgs{fwcsrf} ? $FW_webArgs{fwcsrf} : "";
|
||||
my $want = $defs{$FW_wname}{CSRFTOKEN};
|
||||
if($supplied ne $want) {
|
||||
Log3 $FW_wname, 3, "FHEMWEB $FW_wname CSRF error: $supplied ne $want";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if($FW_inform) { # Longpoll header
|
||||
if($FW_inform =~ /type=/) {
|
||||
@ -658,7 +670,8 @@ FW_answerCall($)
|
||||
|
||||
my $onload = AttrVal($FW_wname, "longpoll", 1) ?
|
||||
"onload=\"FW_delayedStart()\"" : "";
|
||||
FW_pO "</head>\n<body name=\"$t\" $onload>";
|
||||
my $csrf= ($FW_CSRF ? "fwcsrf='$defs{$FW_wname}{CSRFTOKEN}'" : "");
|
||||
FW_pO "</head>\n<body name=\"$t\" $csrf $onload>";
|
||||
|
||||
if($FW_activateInform) {
|
||||
$FW_cmdret = $FW_activateInform = "";
|
||||
@ -679,7 +692,7 @@ FW_answerCall($)
|
||||
foreach my $line (@lines) {
|
||||
$FW_cmdret .= "\n" if( $FW_cmdret );
|
||||
foreach my $word ( split( / /, $line ) ) {
|
||||
$word = "<a href=\"$FW_ME$FW_subdir?detail=$word\">$word</a>"
|
||||
$word = "<a href=\"$FW_ME$FW_subdir?detail=$word$FW_CSRF\">$word</a>"
|
||||
if( $defs{$word} );
|
||||
$FW_cmdret .= "$word ";
|
||||
}
|
||||
@ -921,6 +934,7 @@ FW_makeSelect($$$$)
|
||||
FW_pO "<form method=\"$FW_formmethod\" ".
|
||||
"action=\"$FW_ME$FW_subdir\" autocomplete=\"off\">";
|
||||
FW_pO FW_hidden("detail", $d);
|
||||
FW_pO FW_hidden("fwcsrf", $defs{$FW_wname}{CSRFTOKEN}) if($FW_CSRF);
|
||||
FW_pO FW_hidden("dev.$cmd$d", $d);
|
||||
FW_pO FW_submit("cmd.$cmd$d", $cmd, $class);
|
||||
FW_pO "<div class=\"$class downText\"> $d </div>";
|
||||
@ -967,6 +981,7 @@ FW_doDetail($)
|
||||
|
||||
FW_pO "<form method=\"$FW_formmethod\" action=\"$FW_ME\">";
|
||||
FW_pO FW_hidden("detail", $d);
|
||||
FW_pO FW_hidden("fwcsrf", $defs{$FW_wname}{CSRFTOKEN}) if($FW_CSRF);
|
||||
|
||||
FW_makeSelect($d, "set", FW_widgetOverride($d, getAllSets($d)), "set");
|
||||
FW_makeSelect($d, "get", FW_widgetOverride($d, getAllGets($d)), "get");
|
||||
@ -1156,7 +1171,7 @@ FW_roomOverview($)
|
||||
foreach(my $idx = 0; $idx < @list1; $idx++) {
|
||||
next if(!$list1[$idx]);
|
||||
my $sel = ($list1[$idx] eq $FW_room ? " selected=\"selected\"" : "");
|
||||
FW_pO "<option value='$list2[$idx]'$sel>$list1[$idx]</option>";
|
||||
FW_pO "<option value='$list2[$idx]$FW_CSRF'$sel>$list1[$idx]</option>";
|
||||
}
|
||||
FW_pO "</select></td>";
|
||||
FW_pO "</tr>";
|
||||
@ -1204,6 +1219,7 @@ FW_roomOverview($)
|
||||
FW_pO '<table border="0" class="header"><tr><td style="padding:0">';
|
||||
FW_pO "<form method=\"$FW_formmethod\" action=\"$FW_ME\">";
|
||||
FW_pO FW_hidden("room", "$FW_room") if($FW_room);
|
||||
FW_pO FW_hidden("fwcsrf", $defs{$FW_wname}{CSRFTOKEN}) if($FW_CSRF);
|
||||
FW_pO FW_textfield("cmd", $FW_ss ? 25 : 40, "maininput");
|
||||
FW_pO "</form>";
|
||||
FW_pO "</td></tr></table>";
|
||||
@ -1661,6 +1677,7 @@ FW_style($$)
|
||||
FW_pO FW_textfieldv("saveName", 30, "saveName", $fileName);
|
||||
FW_pO "<br><br>";
|
||||
FW_pO FW_hidden("cmd", "style save $fileName $cfgDB");
|
||||
FW_pO FW_hidden("fwcsrf", $defs{$FW_wname}{CSRFTOKEN}) if($FW_CSRF);
|
||||
FW_pO "<textarea name=\"data\" cols=\"$ncols\" rows=\"30\">" .
|
||||
"$data</textarea>";
|
||||
FW_pO "</form>";
|
||||
@ -1766,7 +1783,7 @@ FW_pH(@)
|
||||
my ($link, $txt, $td, $class, $doRet,$nonl) = @_;
|
||||
my $ret;
|
||||
|
||||
$link = ($link =~ m,^/,) ? $link : "$FW_ME$FW_subdir?$link";
|
||||
$link = ($link =~ m,^/,) ? "$link$FW_CSRF" : "$FW_ME$FW_subdir?$link$FW_CSRF";
|
||||
|
||||
# Using onclick, as href starts safari in a webapp.
|
||||
# Known issue: the pointer won't change
|
||||
@ -1796,6 +1813,7 @@ FW_pHPlain(@)
|
||||
$link = "?$link" if($link !~ m+^/+);
|
||||
my $ret = "";
|
||||
$ret .= "<td>" if($td);
|
||||
$link .= $FW_CSRF;
|
||||
if($FW_ss || $FW_tp) {
|
||||
$ret .= "<a onClick=\"location.href='$FW_ME$FW_subdir$link'\">$txt</a>";
|
||||
} else {
|
||||
@ -1930,6 +1948,20 @@ FW_Attr(@)
|
||||
$modules{FHEMWEB}{AttrList} .= " ".join(" ",@add) if(@add);
|
||||
}
|
||||
|
||||
if($a[2] eq "csrfToken" && $a[0] eq "set") {
|
||||
my $csrf = $a[3];
|
||||
if($csrf eq "random") {
|
||||
my ($x,$y) = gettimeofday();
|
||||
$csrf = rand($y)*rand($x);
|
||||
}
|
||||
$hash->{CSRFTOKEN} = $csrf;
|
||||
}
|
||||
|
||||
if($a[2] eq "csrfToken" && $a[0] eq "del") {
|
||||
delete($hash->{CSRFTOKEN});
|
||||
}
|
||||
|
||||
|
||||
return $retMsg;
|
||||
}
|
||||
|
||||
@ -2096,6 +2128,7 @@ FW_makeEdit($$$)
|
||||
FW_pO "<div id=\"edit\" style=\"display:none\">";
|
||||
FW_pO "<form method=\"$FW_formmethod\">";
|
||||
FW_pO FW_hidden("detail", $name);
|
||||
FW_pO FW_hidden("fwcsrf", $defs{$FW_wname}{CSRFTOKEN}) if($FW_CSRF);
|
||||
my $cmd = "modify";
|
||||
my $ncols = $FW_ss ? 30 : 60;
|
||||
FW_pO "<textarea name=\"val.${cmd}$name\" ".
|
||||
@ -2265,10 +2298,10 @@ FW_devState($$@)
|
||||
$txt = "<a onClick=\"FW_cmd('$FW_ME$FW_subdir?XHR=1&$link')\">$txt</a>";
|
||||
|
||||
} elsif($FW_ss || $FW_tp) {
|
||||
$txt ="<a onClick=\"location.href='$FW_ME$FW_subdir?$link$rf'\">$txt</a>";
|
||||
$txt ="<a onClick=\"location.href='$FW_ME$FW_subdir?$link$rf$FW_CSRF'\">$txt</a>";
|
||||
|
||||
} else {
|
||||
$txt = "<a href=\"$FW_ME$FW_subdir?$link$rf\">$txt</a>";
|
||||
$txt = "<a href=\"$FW_ME$FW_subdir?$link$rf$FW_CSRF\">$txt</a>";
|
||||
|
||||
}
|
||||
}
|
||||
@ -2454,6 +2487,7 @@ FW_dropdownFn()
|
||||
$fwsel = ($cmd eq "state" ? "" : "$cmd ") .
|
||||
FW_select("$d-$cmd","val.$d", \@tv, $txt,"dropdown","submit()").
|
||||
FW_hidden("cmd.$d", "set");
|
||||
$fwsel .= FW_hidden("fwcsrf", $defs{$FW_wname}{CSRFTOKEN}) if($FW_CSRF);
|
||||
|
||||
return "<td colspan='2'><form method=\"$FW_formmethod\">".
|
||||
FW_hidden("arg.$d", $cmd) .
|
||||
@ -3055,6 +3089,15 @@ FW_widgetOverride($$)
|
||||
</code></ul>
|
||||
</li><br>
|
||||
|
||||
<a name="csrfToken"></a>
|
||||
<li>csrfToken<br>
|
||||
If set, FHEMWEB requires the value of this attribute as fwcsrf
|
||||
Parameter for each command. If the value is random, then a random
|
||||
number will be generated on each FHEMWEB start. It is used as
|
||||
countermeasure for Cross Site Resource Forgery attacks.
|
||||
Default is not active.
|
||||
</li><br>
|
||||
|
||||
|
||||
</ul>
|
||||
</ul>
|
||||
@ -3591,6 +3634,16 @@ FW_widgetOverride($$)
|
||||
</code></ul>
|
||||
</li><br>
|
||||
|
||||
<a name="csrfToken"></a>
|
||||
<li>csrfToken<br>
|
||||
Falls gesetzt, wird der Wert des Attributes als fwcsrf Parameter bei
|
||||
jedem ueber FHEMWEB abgesetzten Kommando verlangt. Falls der Wert
|
||||
random ist, dann wird ein Zufallswert beim jeden FHEMWEB Start neu
|
||||
generiert.
|
||||
Es dient zum Schutz von Cross Site Resource Forgery Angriffen.
|
||||
Default ist leer, also nicht aktiv.
|
||||
</li><br>
|
||||
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
|
@ -2,6 +2,8 @@ var consConn;
|
||||
|
||||
var isFF = (navigator.userAgent.toLowerCase().indexOf('firefox') > -1);
|
||||
|
||||
log("Console is opening");
|
||||
|
||||
function
|
||||
consUpdate()
|
||||
{
|
||||
@ -29,6 +31,7 @@ consFill()
|
||||
var query = document.location.pathname+"?XHR=1"+
|
||||
"&inform=type=raw;filter=.*"+
|
||||
"×tamp="+new Date().getTime();
|
||||
query = addcsrf(query);
|
||||
consConn.open("GET", query, true);
|
||||
consConn.onreadystatechange = consUpdate;
|
||||
consConn.send(null);
|
||||
|
@ -14,9 +14,21 @@ log(txt)
|
||||
console.log(txt);
|
||||
}
|
||||
|
||||
function
|
||||
addcsrf(arg)
|
||||
{
|
||||
var oarg=arg;
|
||||
var csrf = document.body.getAttribute('fwcsrf');
|
||||
if(csrf && arg.indexOf('fwcsrf') < 0)
|
||||
arg += '&fwcsrf='+csrf;
|
||||
log(oarg+" -> "+arg);
|
||||
return arg;
|
||||
}
|
||||
|
||||
function
|
||||
FW_cmd(arg) /* see also FW_devState */
|
||||
{
|
||||
arg = addcsrf(arg);
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("GET", arg, true);
|
||||
req.send(null);
|
||||
@ -168,6 +180,7 @@ FW_longpoll()
|
||||
var query = document.location.pathname+"?XHR=1"+
|
||||
"&inform=type=status;filter="+filter+
|
||||
"×tamp="+new Date().getTime();
|
||||
query = addcsrf(query);
|
||||
FW_pollConn.open("GET", query, true);
|
||||
FW_pollConn.onreadystatechange = FW_doUpdate;
|
||||
FW_pollConn.send(null);
|
||||
@ -284,7 +297,9 @@ FW_queryValue(cmd, qFn, qArg)
|
||||
eval(qFn.replace("%", qResp));
|
||||
delete qConn;
|
||||
}
|
||||
qConn.open("GET", document.location.pathname+"?cmd="+cmd+"&XHR=1", true);
|
||||
var query = document.location.pathname+"?cmd="+cmd+"&XHR=1"
|
||||
query = addcsrf(query);
|
||||
qConn.open("GET", query, true);
|
||||
qConn.send(null);
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,8 @@ colorpicker_setColor(el,mode,cmd)
|
||||
}
|
||||
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("GET", cmd.replace('%',v), true);
|
||||
var qcmd = addcsrf(cmd.replace('%',v));
|
||||
req.open("GET", qcmd, true);
|
||||
req.send(null);
|
||||
|
||||
if( 0 )
|
||||
|
@ -35,7 +35,8 @@ textField_setText(el,cmd)
|
||||
{
|
||||
var v = el.value;
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("GET", cmd.replace('%',v), true);
|
||||
var qcmd = addcsrf(cmd.replace('%',v));
|
||||
req.open("GET", qcmd, true);
|
||||
req.send(null);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
@import url("defaultCommon.css");
|
||||
|
||||
#console { height:auto; }
|
||||
textarea { font-family:Arial, sans-serif; font-size:16px;}
|
||||
#back { position:absolute; top: 2px; left:18px; }
|
||||
#logo { position:absolute; top: 2px; left: 2px;
|
||||
|
Loading…
Reference in New Issue
Block a user