Yesterday’s PHP Passthru Output Buffering Security Tutorial set up a framework for our tightened “defence” of our “Passthru” (PHP) web application woooooooorrrrrrrrlllllld view, but there is scope, still, to improve the “offence”, a move which affects “defence” as well (wouldn’t you know it?!).
That improvement is to …
genericize the verb
… which we find ourselves commonly doing (in our) programming, because of our penchant for functional (ie. action “verb”) rather than “object oriented” (ie. subject or object “noun”) way of writing the code. In other words we tend to think of the whole problem in terms of action points to provide a solution, rather than thinking of the players and working out the roles and behaviours of those players. It’s no big deal which approach you take, as long as you end up with the correct result, in a reasonably timely manner.
For us, we often model a first draft of such a functional program (or web application) on the “verb” of primary interest, resulting in our first draft containing several hardcodings of the word (or “verb”) “whatis”. And so, “genericization”, for us, is often to turn that/those “hardcoding(s)” into a variable that can be shared across that code base, hence the “early on” PHP codeline …
<?php
$verb="whatis";
?>
… as a default first assumption (for the genericization), and a friendly approach for backward compatibility (another personal desire with our code). What is such a “genericization drive”‘s favourite accomplice? For us, it is an HTML select (dropdown) element, as a …
- where there was one, “plug in” several
- take up little extra webpage room doing this
- the programmer controls the option contents
… the stringency here being that, even for the “faux textbox entry possibilities” (because, yes, this “verb” will require its own separate argument (or will it?!)), you must make sure its value is non-blank, and hence, as mentioned in that last point above, “the programmer controls the option contents”, and in that way, can control the implications of the “offence” additional functionality to the concerns of the “defence” security concerns of the programmer and web server hosting that PHP web application.
So feel free to try out third changed whatis.php whatis.php third incarnation of our “Passthru” verb (report) web application.
Back to that “(or will it?!)” question above, yes, for Curl purposes, where we try, for user’s sanity, to never involve more than one URL argument we allow the proposed “operating system” verb equate to the “name” in the classic …
name=value
… relative database (and other) architectures, allowing Curl users to encapsulate all they wish to convey to our web application in the one …
curl HTTP://localhost:8888/whatis.php?ping=127.0.0.1
… example of a local (MAMP) web application call via curl (on the command line).
Notice with the code below how we manipulate switches to help some operating system command line commands limit themselves to one iteration …
<?php
// whatis.php
// Supervise whatis command
// November, 2020
$verb="whatis";
function retpins($pins, $reld) {
global $verb;
if ($verb == "who" && trim(str_replace("+"," ",urldecode($pins))) == "") {
$pins=urlencode("-H");
if ($reld) {
echo "<html><body onload=\"location.href=document.URL.split('#')[0] + encodeURIComponent('-H');\"></body></html>";
exit;
}
} else if ($verb == "screencapture" && trim(str_replace("+"," ",urldecode($pins))) == "") {
$pins=urlencode("-c");
if ($reld) {
echo "<html><body onload=\"location.href=document.URL.split('#')[0] + encodeURIComponent('-c');\"></body></html>";
exit;
}
} else if ($verb == "ping" && trim(str_replace("+"," ",urldecode($pins))) == "") {
$pins=urlencode("-o");
if ($reld) {
echo "<html><body onload=\"location.href=document.URL.split('#')[0] + encodeURIComponent('-o');\"></body></html>";
exit;
}
} else if ($verb == "top" && trim(str_replace("+"," ",urldecode($pins))) == "") {
$pins=urlencode("-l 1");
if ($reld) {
echo "<html><body onload=\"location.href=document.URL.split('#')[0] + encodeURIComponent('-l 1');\"></body></html>";
exit;
}
} else if ($verb == "tree" && trim(str_replace("+"," ",urldecode($pins))) == "") {
$pins=urlencode("c:");
if ($reld) {
echo "<html><body onload=\"location.href=document.URL.split('#')[0] + encodeURIComponent('c:');\"></body></html>";
exit;
}
} else if (trim(str_replace("+"," ",urldecode($pins))) == "") { // if ($verb == "uptime" && trim(str_replace("+"," ",urldecode($pins))) == "") {
$pins=urlencode(";");
if ($reld) {
echo "<html><body onload=\"location.href=document.URL.split('#')[0] + encodeURIComponent(';');\"></body></html>";
exit;
}
}
return $pins;
}
function maybe($iv) {
if ($iv == "ping") { return "ping -o"; }
return $iv;
}
$verbs=array("whatis", "man", "jobs", "ping", "uname", "help");
$insh='insearch';
$sayopt='';
$meth="POST";
if (strpos(("~" . strtolower($_SERVER['SERVER_NAME'])), '~localhost') !== false) { $meth="GET"; }
if (PHP_OS === "Darwin") {
array_push($verbs, "say", "textutil", "screencapture", "who", "uptime", "top", "hostname");
} else if ($meth == "GET" && strtoupper(substr((PHP_OS . ' '), 0, 3)) === 'WIN') {
$verbs=array("tree", "ping", "help", "echo");
$verb="tree";
} else { //if ($meth == "GET") {
array_push($verbs, "uptime", "echo");
}
foreach ($verbs as $value) {
$sayopt.='<option value=' . $value . '>' . $value . '</option>';
}
$gb="";
try {
if (strpos(('~' . $_SERVER['HTTP_USER_AGENT']), '~curl') === false) {
$gb="y";
}
} catch (Exception $ee) {
}
if (isset($_POST['verb'])) {
if (trim(str_replace("+"," ",urldecode($_POST['verb']))) != '') { $verb=str_replace("+"," ",urldecode($_POST['verb'])); }
} else if (isset($_GET['verb'])) {
if (trim(str_replace("+"," ",urldecode($_GET['verb']))) != '') { $verb=str_replace("+"," ",urldecode($_GET['verb'])); }
}
foreach ($verbs as $value) {
if (isset($_POST[$value])) {
$insh=$value;
$verb=$value;
} else if (isset($_GET[$value])) {
$insh=$value;
$verb=$value;
}
}
$pins='';
if (isset($_POST[$insh]) && strpos(strtolower('' . $_SERVER['HTTP_REFERER']), "rjmprogramming.com.au") !== false) {
$pins=retpins($_POST[$insh],false);
} else if (isset($_GET[$insh]) && strpos(strtolower('' . $_SERVER['HTTP_REFERER']), "/localhost") !== false) {
$pins=retpins($_GET[$insh],true);
} else if ($meth == "GET" && isset($_GET[$insh])) {
$pins=$_GET[$insh];
if ($verb == "who" && trim(str_replace("+"," ",urldecode($pins))) == "") {
$pins=urlencode("-H");
} else if ($verb == "screencapture" && trim(str_replace("+"," ",urldecode($pins))) == "") {
$pins=urlencode("-c");
} else if ($verb == "ping" && trim(str_replace("+"," ",urldecode($pins))) == "") {
$pins=urlencode("-l 1");
} else if ($verb == "top" && trim(str_replace("+"," ",urldecode($pins))) == "") {
$pins=urlencode("-o");
} else if ($verb == "tree" && trim(str_replace("+"," ",urldecode($pins))) == "") {
$pins=urlencode("c:");
} else if (trim(str_replace("+"," ",urldecode($pins))) == "") {
$pins=urlencode(";");
} else {
$pins=$_GET[$insh];
}
} else if ($meth == "GET") {
$pins=' ';
}
if (trim($pins) != '' && !isset($argv)) {
if (urldecode($pins) == '') {
echo "<html><head><title>Operating System " . maybe($verb) . " -- RJM Programming - November, 2020</title></head><body onload=\" document.getElementById('insearch').focus(); \"><form action=./whatis.php method=" . $meth . "><h1>Supervise <select title=verb name=verb>" . $sayopt . "</select> command</h1><h3>RJM Programming - November, 2020</h3><br><br><input placeholder='Enter object' type=text id=insearch name=insearch value=''></input> <input type=submit value=Go></input></form></body></html>";
} else if ($meth == "GET" && $gb == "") {
passthru(maybe($verb) . " " . explode("\n",explode("\r",explode(";",explode("<",explode(">",explode("|",str_replace("+"," ",urldecode($pins)))[0])[0])[0])[0])[0])[0]);
} else {
ob_start();
passthru(maybe($verb) . " " . explode("\n",explode("\r",explode(";",explode("<",explode(">",explode("|",str_replace("+"," ",urldecode($pins)))[0])[0])[0])[0])[0])[0]);
$var = ob_get_contents();
ob_end_clean();
echo "<html><head><title>Operating System " . maybe($verb) . " " . explode("\n",explode("\r",explode(";",explode("<",explode(">",explode("|",str_replace("+"," ",urldecode($pins)))[0])[0])[0])[0])[0])[0] . " - RJM Programming -- November, 2020</title></head><body onload=\" document.getElementById('insearch').focus(); \"><pre>$ " . $verb . " " . explode("\n",explode("\r",explode(";",explode("<",explode(">",explode("|",str_replace("+"," ",urldecode($pins)))[0])[0])[0])[0])[0])[0] . "<br>" . str_replace("\n", "<br>", $var) . "</pre><br><br><form action=./whatis.php method=" . $meth . "><h1>Supervise <select title=verb name=verb>" . $sayopt . "</select> command</h1><h3>RJM Programming - November, 2020</h3><br><br><input placeholder='Enter object' type=text id=insearch name=insearch value=''></input> <input type=submit value=Go></input></form></body></html>";
exit;
}
} else if (!isset($argv) && ($pins == '' || (trim($pins) == '' && $meth == "GET")) && !isset($_GET[$insh]) && $gb != "") {
echo "<html><head><title>Operating System verb - RJM Programming - November, 2020</title></head><body onload=\" document.getElementById('insearch').focus(); \"><form action=./whatis.php method=" . $meth . "><h1>Supervise <select title=verb name=verb>" . $sayopt . "</select> command</h1><h3>RJM Programming - November, 2020</h3><br><br><input placeholder='Enter object' type=text id=insearch name=insearch value=''></input> <input type=submit value=Go></input></form></body></html>";
}
?>
Previous relevant PHP Passthru Output Buffering Security Tutorial is shown below.
Serverside web applications (such as this current PHP one), more so than purely clientside web applications, are a bit like a lot of sports …
- there is the “offence” functionality side that provides a product or service to the user … as well as …
- there is the “defence” functionality side, depending on the architecture of the web application, that concerns itself with security
… and our signature “Passthru” “whatis” web application ticks a lot of the boxes asking for a good “defence”. Why?
- the (PHP serverside) web application accepts user input from a textbox, and feeds it back to itself via an HTML form, to perform more underlying operating system actions
- in the first incarnation of whatis.php we set that form to method=GET … which makes it just too easy to have the worries below be a relevant (bad for security) issue
- if that first incarnation of whatis.php was still current on the rjmprogramming.com.au website we could have circumvented any textbox validation we might have implemented (in a clientside sense) with whatever a savvy user might want to refeed into the web application via either …
- web address bar URL like (for “whatis find”) …
HTTP://www.rjmprogramming.com.au/PHP/whatis.php?insearch=find
- curl (command line entry (for “whatis find”)) …
curl HTTP://www.rjmprogramming.com.au/PHP/whatis.php?insearch=find
… as benign examples of the modus operandi
- web address bar URL like (for “whatis find”) …
… but what if the motivations of a user are not benign? Well, yesterday’s Stop Press Oops! intervention is a case in point. It is a …
- security issue, up to the programmer, for such “underlying operating system” leaning web applications to close things down as far as the role that textbox user input (or faux ones) so that you do not allow (and in doing so, not needing (and so we are not doing any as of this second incarnation) any textbox validation that could cut off at a semicolon or less than sign or greater than sign or vertical (pipe) line character or line feed or carriage return, that textbox validation though could be used as a “virtue signal”) …
- the user input data to contain a multi-command aspect to it … via scrutiny of $_POST[‘insearch’] and/or $_GET[‘insearch’] (if a localhost web server name in $_SERVER[‘SERVER_NAME’])
- the user input data, yesterday and today, all along, at least ties down that “verb” word of the “operating system via PHP
passthru” command line command first (and all important) word (ie. hardcoded “whatis “) - the user input data, yesterday and today, all along, at least ties down that “verb” word of the “operating system via PHP passthru” command line command switches the user might define that cause web server damage (though with “whatis” we could not think of any)
- call of web application must come from expected place … via examination of $_SERVER[‘REFERER’]
… the HTML form method=POST preferable to method=GET for those reasons already talked about
- limit modes of use as per table below …
PHP Mode of use localhost web server rjmprogramming.com.au web server Surfing the web … no “whatis” user input Allowed (shows textbox for user input) Allowed (shows textbox for user input) Surfing the web … “whatis” user input Allowed (method=GET) Allowed (method=POST only) Curl … no “whatis” user input Allowed (nothing returned) Not allowed (nothing returned) Curl (NB. curl can (method=)POST) … “whatis” user input Allowed (via command line ? argument simulating method=GET) Not allowed (nothing returned) Command line … no “whatis” user input Not allowed (nothing returned) Not allowed (nothing returned) Command line … “whatis” user input Erroneous (Could not open input file: whatis.php?insearch=find) Erroneous (Could not open input file: whatis.php?insearch=find)
… causing what we hope to be a similarly functional second changed whatis.php, yet considerably more secure whatis.php second incarnation of our “Passthru” “whatis” (report) web application improvement to PHP Passthru Output Buffering Primer Tutorial …
<?php
// whatis.php
// Supervise whatis command
// November, 2020
$meth="POST";
if (strpos(("~" . strtolower($_SERVER['SERVER_NAME'])), '~localhost') !== false) { $meth="GET"; }
$gb="";
try {
if (strpos(('~' . $_SERVER['HTTP_USER_AGENT']), '~curl') === false) {
$gb="y";
}
} catch (Exception $ee) {
}
$pins='';
if (isset($_POST['insearch']) && strpos(strtolower('' . $_SERVER['HTTP_REFERER']), "rjmprogramming.com.au") !== false) {
$pins=$_POST['insearch'];
} else if (isset($_GET['insearch']) && strpos(strtolower('' . $_SERVER['HTTP_REFERER']), "/localhost") !== false) {
$pins=$_GET['insearch'];
} else if ($meth == "GET" && isset($_GET['insearch'])) {
$pins=$_GET['insearch'];
} else if ($meth == "GET") {
$pins=' ';
}
if (trim($pins) != '' && !isset($argv)) {
if (urldecode($pins) == '') {
echo "<html><body onload=\" document.getElementById('insearch').focus(); \"><h1>Supervise whatis command</h1><h3>RJM Programming - November, 2020</h3><br><br><form action=./whatis.php method=" . $meth . "><input placeholder='Enter whatis object' type=text id=insearch name=insearch value=''></input> <input type=submit value=Go></input></form></body></html>";
} else if ($meth == "GET" && $gb == "") {
passthru("whatis " . explode("\n",explode("\r",explode(";",explode("<",explode(">",explode("|",str_replace("+"," ",urldecode($pins)))[0])[0])[0])[0])[0])[0]);
} else {
ob_start();
passthru("whatis " . explode("\n",explode("\r",explode(";",explode("<",explode(">",explode("|",str_replace("+"," ",urldecode($pins)))[0])[0])[0])[0])[0])[0]);
$var = ob_get_contents();
ob_end_clean();
echo "<html><body onload=\" document.getElementById('insearch').focus(); \"><pre>$ whatis " . explode("\n",explode("\r",explode(";",explode("<",explode(">",explode("|",str_replace("+"," ",urldecode($pins)))[0])[0])[0])[0])[0])[0] . "<br>" . str_replace("\n", "<br>", $var) . "</pre><br><br><h1>Supervise whatis command</h1><h3>RJM Programming - November, 2020</h3><br><br><form action=./whatis.php method=" . $meth . "><input placeholder='Enter whatis object' type=text id=insearch name=insearch value=''></input> <input type=submit value=Go></input></form></body></html>";
exit;
}
} else if (!isset($argv) && ($pins == '' || (trim($pins) == '' && $meth == "GET")) && !isset($_GET[$insh]) && $gb != "") {
echo "<html><body onload=\" document.getElementById('insearch').focus(); \"><h1>Supervise whatis command</h1><h3>RJM Programming - November, 2020</h3><br><br><form action=./whatis.php method=" . $meth . "><input placeholder='Enter whatis object' type=text id=insearch name=insearch value=''></input> <input type=submit value=Go></input></form></body></html>";
}
?>
Previous relevant PHP Passthru Output Buffering Primer Tutorial is shown below.
The PHP passthru command …
Execute an external program and display raw output
… is a good way for your underlying command line be expressed within a PHP web application executed in a web browser.
But that “raw” above is just that, very “raw” in that the neat reports you see with Linux and macOS commands (at the command line) will not show that neatly just by using “passthru”.
But you can use PHP output buffers as per (but there’s more) …
<?php
// whatis.php
// Supervise whatis command
// November, 2020
if (isset($_GET['insearch'])) {
if (urldecode($_GET['insearch']) == '') {
echo "<html><body onload=\" document.getElementById('insearch').focus(); \"><h1>Supervise whatis command</h1><h3>RJM Programming - November, 2020</h3><br><br><form action=./whatis.php method=GET><input placeholder='Enter whatis object' type=text id=insearch name=insearch value=''></input> <input type=submit value=Go></input></form></body></html>";
} else {
ob_start();
passthru("whatis " . explode("\n",explode("\r",explode(";",explode("<",explode(">",explode("|",str_replace("+"," ",urldecode($_GET['insearch'])))[0])[0])[0])[0])[0])[0]);
$var = ob_get_contents();
ob_end_clean();
echo "<html><body onload=\" document.getElementById('insearch').focus(); \"><pre>" . "$ whatis " . explode("\n",explode("\r",explode(";",explode("<",explode(">",explode("|",str_replace("+"," ",urldecode($_GET['insearch'])))[0])[0])[0])[0])[0])[0] . "<br>" . str_replace("\n", "<br>", $var) . "</pre><br><br><h1>Supervise whatis command</h1><h3>RJM Programming - November, 2020</h3><br><br><form action=./whatis.php method=GET><input placeholder='Enter whatis object' type=text id=insearch name=insearch value=''></input> <input type=submit value=Go></input></form></body></html>";
exit;
}
} else {
echo "<html><body onload=\" document.getElementById('insearch').focus(); \"><h1>Supervise whatis command</h1><h3>RJM Programming - November, 2020</h3><br><br><form action=./whatis.php method=GET><input placeholder='Enter whatis object' type=text id=insearch name=insearch value=''></input> <input type=submit value=Go></input></form></body></html>";
}
?>
… to effectively reinstate those carriage return/line feeds that make the “passthru” (underlying operating system) commands those neat reports appealing to our eyes.
Better on a local MAMP web server (under macOS operating system) is today’s proof of concept whatis.php‘s “whatis” command supervisor PHP web application for you to try out (or perhaps download to a local web server).
Stop Press
Oops!
If this was interesting you may be interested in this too.
If this was interesting you may be interested in this too.
If this was interesting you may be interested in this too.