I'm using this script to receive base 64 encoded images from an Android app. I was wondering if there's any way possible to bypass a PHP shell command inside a POST request and get it to work in the server, for example, sending the command shell encoded and a name like "shell.php", "shell.php%0delete0". According to the script, everything will be saved as .png, so I'd say it's safe, but maybe I'm wrong and the script is actually vulnerable to shell command uploads.
<?php
header('charset=utf-8');
if($_SERVER['REQUEST_METHOD'] == 'POST') {
if(isset($_POST['image']) && isset($_POST['name'])) {
$image = $_POST['image'];
$name = $_POST['name'];
file_put_contents("/var/www/html/admigas/android/uploads/$name".strval(date('_Ym')).".png",base64_decode($image));
echo "Success";
} else {
echo "Wrong params";
}
} else {
echo "Nothing to do";
}
?>
So, let's look at the code step-by-step. My comments are preceded by #:
<?php
header('charset=utf-8');
if($_SERVER['REQUEST_METHOD'] == 'POST') {
if(isset($_POST['image']) && isset($_POST['name'])) {
# This is provided by the user:
$image = $_POST['image'];
# This is provided by the user:
$name = $_POST['name'];
# Where does $name come from? User input, of course!
file_put_contents("/var/www/html/admigas/android/uploads/$name".strval(date('_Ym')).".png",base64_decode($image));
echo "Success";
} else {
echo "Wrong params";
}
} else {
echo "Nothing to do";
}
Consequently, sending name=../../../../reverse-shell.php%00 with a base64-encoded payload will allow an attacker to upload an arbitrary file (in this case, PHP code), and probably execute it on the subsequent PHP request.
Solution (presented in diff format):
- $name = $_POST['name'];
+ $name = preg_replace('#[^A-Za-z0-9\-_]#', '', $_POST['name']);
You definitely want to remove any non-URL-safe characters to prevent attacks. You may want to totally rethink your strategy, of course.
Related
I'm trying to figure out why one form works but another one of mine does not. I'm trying to create a form that lets the user choose various bundles, enter a password, and then have the ability to download said files if it's correct. I'm able to do this with one file like so
<?php
// Get the password
$pw = md5($_POST['password']);
// Compare against the stored password
$valid_pw = md5("example");
if($pw != $valid_pw){
echo "Error! You do not have access to this file";
}else{
header("Location:files\bundle1.pdf");
}
?>
However, I cannot get this to work. When I make a selection, enter the password and hit submit, I see a blank screen and the URL is of the PHP file. Here is the code:
<?php
// Get the password
$pw = md5($_POST['password']);
$bundle1 = ($_POST['1']);
$bundle2 = ($_POST['2']);
$bundle3 = ($_POST['3']);
$bundle4 = ($_POST['4']);
// Compare against the stored password
$valid_pw = md5("example");
if($pw != $valid_pw){
echo "Error! You do not have access to this file";
}else{
$bundle1 = 'unchecked';
$bundle2 = 'unchecked';
$bundle3 = 'unchecked';
$bundle4 = 'unchecked';
if (isset($_POST['download'])) {
$selected_radio = $_POST['bundle'];
if ($selected_radio == '1') {
$bundle1 = 'checked';
header("Location:files\bundle1.zip");
exit();
}else if ($selected_radio == '2') {
$bundle2 = 'checked';
header("Location:files\bundle2.zip");
exit(); //added exits so program wouldn't continue to run after selection -k
}
else if ($selected_radio == '3') {
$bundle3 = 'checked';
header("Location:files\bundle3.zip");
exit();
}
else if ($selected_radio == '4') {
$bundle4 = 'checked';
header("Location:files\bundle4.zip");
exit();
}
}
}
?>
The main difference (besides the else if statements) is that the working file is a .PDF and the nonworking ones are .zip. Does that make a difference?
In double quotes php uses \ as escape sequence so instead use \ or /
use
header("Location:files\\bundle1.zip"); or header("Location:files/bundle1.zip");
use forwars slash or double backslash for escape and actual slash
Your paths are wrong:
header("Location:files\bundle1.zip");
^---
HTTP uses / for path separators in URLs, no backslashes. Since \b has no special meaning in a PHP "-quoted string, unlike \n, \r etc... it'll simply be ignored, and you're sending
Location:filesbundle1.zip
to the client.
I'm currently putting together a small web-based GUI to generate kickstart-scripts. I got a confirmation page that's sending the relevant data via POST to the PHP-page where the actual shell script is called to build the iso. So far it's working, but the page seems to execute the script before it outputs anything else (for example, the 'echo' I put in at the beginning of the page ...), and I'm absolutely clueless why. Would anyone care to enlighten me?
Here's the code to the PHP-page that's executing the shell script ...
echo 'Generating your ISO; this might take a while...';
sleep(20);
if (!isset($_POST['auth'])) {
$ad = 'N';
}
else {
$ad = 'Y';
}
if (!isset($_POST['oracle'])) {
$oracle = 'N';
}
else {
$oracle = 'Y';
}
if ((!isset($_POST['ip'])) or (!isset($_POST['hostname'])) or (!isset($_POST['rhsel'])) or (!isset($_POST['submit'])) or (!isset($_POST['gw'])) or (!isset($_POST['nm']))) {
die('Please use the correct form !');
}
if (isset($_POST['ip'])) {
$ip = trim($_POST['ip']);
}
if (isset($_POST['gw'])) {
$gw = trim($_POST['gw']);
}
if (isset($_POST['nm'])) {
$nm = trim($_POST['nm']);
}
if (isset($_POST['hostname'])) {
$hostname = trim($_POST['hostname']);
}
if (isset($_POST['rhsel'])) {
$rhsel = $_POST['rhsel'];
}
passthru("/usr/bin/sudo /data/skripte/webconfig.sh $rhsel $oracle $ad $ip $gw $nm $hostname 2>&1");
PHP scripts accessed via a browser are request-response, meaning all processing is done on the server prior to headers and content being sent to the client. This means you will not get a continually updating output like you would see on the command line. There is no way around this. Sorry.
I want to implement recaptcha in a very simple form
I have a index.html file on client-side, and a post.php server side.
I've tried to integrate recaptcha on the server site, as you can see in my code bellow.
I've made some tests, that seem to have an expected result...
The problem appeard when I tried this query
for X in `seq 0 100`; do curl -D - "http://example.com/post.php" -d
"email=email${X}%40example.com&tos=on&g-recaptcha-response[]=plm&submit="; done
The result was that I've bypassed recaptcha succesfully, and I'm not sure what the problem is.
Most probably, there's a problem in my php code, but what exactly?
post.php
<?php
$email;$submit;$captcha;
if(isset($_POST['submit']))
{
$email=filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
}
if(isset($_POST['g-recaptcha-response']))
{
$captcha=$_POST['g-recaptcha-response'];
}
if(!$captcha)
{
echo '<h2>Please check the the captcha form.</h2>';
exit;
}
$response=file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=6Le[whatever[7_t&response=".$captcha."&remoteip=".$_SERVER['REMOTE_ADDR']);
if($response.success==false)
{
echo '<h2>You are spammer ! Get the #$%K out</h2>';
}
else
{
$file = 'email-list.txt';
if (filter_var($email, FILTER_VALIDATE_EMAIL))
{
if(!(exec('grep '.escapeshellarg($email).' '.$file)))
{
// Open the file to get existing content
$current = file_get_contents($file);
// Append a new person to the file
$current .= $email . "\n";
// Write the contents back to the file
file_put_contents($file, $current);
header('Location: index.html?success='.urlencode($email));
}
else
header('Location: index.html?fail='.urlencode($email));
}
else
{
echo "$email is <strong>NOT</strong> a valid email address.<br/><br/>";
}
}
?>
index.html
...
<div class="form-group" ng-cloak>
<div class="g-recaptcha" ng-show="IAgree" data-sitekey="6LeEW[whatever]-UXo3"></div>
</div>
...
How can I solve this? English is not my native language; please excuse typing errors.
As mentioned in my comments above - file_get_contents returns a string. You need to decode the json string into a php object using the json_decode function:
$url = "https://www.google.com/recaptcha/api/siteverify?"
$response = json_decode(file_get_contents($url));
if($response->success == false) {
echo "Oh no";
}
How can you mimic a command line run of a script with arguements inside a PHP script? Or is that not possible?
In other words, let's say you have the following script:
#!/usr/bin/php
<?php
require "../src/php/whatsprot.class.php";
function fgets_u($pStdn) {
$pArr = array($pStdn);
if (false === ($num_changed_streams = stream_select($pArr, $write = NULL, $except = NULL, 0))) {
print("\$ 001 Socket Error : UNABLE TO WATCH STDIN.\n");
return FALSE;
} elseif ($num_changed_streams > 0) {
return trim(fgets($pStdn, 1024));
}
}
$nickname = "WhatsAPI Test";
$sender = ""; // Mobile number with country code (but without + or 00)
$imei = ""; // MAC Address for iOS IMEI for other platform (Android/etc)
$countrycode = substr($sender, 0, 2);
$phonenumber=substr($sender, 2);
if ($argc < 2) {
echo "USAGE: ".$_SERVER['argv'][0]." [-l] [-s <phone> <message>] [-i <phone>]\n";
echo "\tphone: full number including country code, without '+' or '00'\n";
echo "\t-s: send message\n";
echo "\t-l: listen for new messages\n";
echo "\t-i: interactive conversation with <phone>\n";
exit(1);
}
$dst=$_SERVER['argv'][2];
$msg = "";
for ($i=3; $i<$argc; $i++) {
$msg .= $_SERVER['argv'][$i]." ";
}
echo "[] Logging in as '$nickname' ($sender)\n";
$wa = new WhatsProt($sender, $imei, $nickname, true);
$url = "https://r.whatsapp.net/v1/exist.php?cc=".$countrycode."&in=".$phonenumber."&udid=".$wa->encryptPassword();
$content = file_get_contents($url);
if(stristr($content,'status="ok"') === false){
echo "Wrong Password\n";
exit(0);
}
$wa->Connect();
$wa->Login();
if ($_SERVER['argv'][1] == "-i") {
echo "\n[] Interactive conversation with $dst:\n";
stream_set_timeout(STDIN,1);
while(TRUE) {
$wa->PollMessages();
$buff = $wa->GetMessages();
if(!empty($buff)){
print_r($buff);
}
$line = fgets_u(STDIN);
if ($line != "") {
if (strrchr($line, " ")) {
// needs PHP >= 5.3.0
$command = trim(strstr($line, ' ', TRUE));
} else {
$command = $line;
}
switch ($command) {
case "/query":
$dst = trim(strstr($line, ' ', FALSE));
echo "[] Interactive conversation with $dst:\n";
break;
case "/accountinfo":
echo "[] Account Info: ";
$wa->accountInfo();
break;
case "/lastseen":
echo "[] Request last seen $dst: ";
$wa->RequestLastSeen("$dst");
break;
default:
echo "[] Send message to $dst: $line\n";
$wa->Message(time()."-1", $dst , $line);
break;
}
}
}
exit(0);
}
if ($_SERVER['argv'][1] == "-l") {
echo "\n[] Listen mode:\n";
while (TRUE) {
$wa->PollMessages();
$data = $wa->GetMessages();
if(!empty($data)) print_r($data);
sleep(1);
}
exit(0);
}
echo "\n[] Request last seen $dst: ";
$wa->RequestLastSeen($dst);
echo "\n[] Send message to $dst: $msg\n";
$wa->Message(time()."-1", $dst , $msg);
echo "\n";
?>
To run this script, you are meant to go to the Command Line, down to the directory the file is in, and then type in something like php -s "whatsapp.php" "Number" "Message".
But what if I wanted to bypass the Command Line altogether and do that directly inside the script so that I can run it at any time from my Web Server, how would I do that?
First off, you should be using getopt.
In PHP it supports both short and long formats.
Usage demos are documented at the page I've linked to. In your case, I suspect you'll have difficulty detecting whether a <message> was included as your -s tag's second parameter. It will probably be easier to make the message a parameter for its own option.
$options = getopt("ls:m:i:");
if (isset($options["s"] && !isset($options["m"])) {
die("-s needs -m");
}
As for running things from a web server ... well, you pass variables to a command line PHP script using getopt() and $argv, but you pass variables from a web server using $_GET and $_POST. If you can figure out a sensible way to map $_GET variables your command line options, you should be good to go.
Note that a variety of other considerations exist when taking a command line script and running it through a web server. Permission and security go hand in hand, usually as inverse functions of each other. That is, if you open up permissions so that it's allowed to do what it needs, you may expose or even create vulnerabilities on your server. I don't recommend you do this unless you'll more experienced, or you don't mind if things break or get attacked by script kiddies out to 0wn your server.
You're looking for backticks, see
http://php.net/manual/en/language.operators.execution.php
Or you can use shell_exec()
http://www.php.net/manual/en/function.shell-exec.php
i am trying to use the !isset on the '$class' variable to see if it has a value or not, and then base the mysql_query function on that. but it's a no go. see anything wrong?
<?php session_start();
$heyyou = $_SESSION['usern'];
$points = $_SESSION['points'];
$school = $_SESSION['school'];
$class = $_POST['class'];
$prof = $_POST['prof'];
$date = $_POST['dater'];
$fname = $_FILES['fileToUpload']["name"];
?>
<div id='contenttext' class='contenttext'>
<?php
#mysql_select_db($database) or die( "Unable to select database");
$query = "INSERT INTO uploadedfiles (usename, filename, date, teacher, class) VALUES ('$heyyou', '$fname', '$date', '$prof', '$class')";
if (!isset($class)){
echo 'You need to pick a class for the content'; }
else{
mysql_query($query); }
mysql_close();
?>
<?php
if (($_FILES["fileToUpload"]["type"] == "image/gif" || $_FILES["fileToUpload"]["type"] == "image/jpeg" || $_FILES["fileToUpload"]["type"] == "image/png") && $_FILES["fileToUpload"]["size"] < 10000000)
{
move_uploaded_file($_FILES["fileToUpload"]["tmp_name"],
"upload/" . $_FILES["fileToUpload"]["name"]);
echo "Your file has successfully been uploaded, and is awaiting moderator approval for points." . "<html><br><a href='uploadfile.php'>Upload more.</a>";
}
else
{
echo "Files must be either JPEG, GIF, or PNG and less than 10,000 kb";
}
?>
</div>
</body>
</html>
Two major security problems with your code:
You're wide open to SQL injection attacks (see: http://bobby-tables.com/)
You're blindly trusting the user is not malicious for the file upload. The ['type'] and ['name'] fields are completely under user control, and it's trivial to hack the upload to say it's a gif while still uploading a PHP script. You then use the user-supplied filename, WHICH CAN CONTAIN PATH INFORMATION, and dump it directly to your server. This leaves the door wide open to a malicious user uploading any file they want, anywhere on the server.
Minor point #3:
You don't check if the database query succeeds. Never assume a query succeeds. Even if the SQL statement is perfectly valid, there's far too many other reasons that could make it fail anyways. Always check the query call with ... = mysql_query(...) or die(mysql_error()) as a bare minimum error handler.
Probably because $class is being set, by you. Try if (empty($class)){
I maybe wrong but class is a reserved word try another name and $class != ""
http://www.php.net/manual/en/reserved.keywords.php
BTW remove you DB Conect info please we me be nice but some of the people reading this may not be. ;-)
Try this, first initialize all your variables and then assign the POST values.
Eg:
$class='';
$class = $_POST['class'];
if (!isset($class)){
echo 'You need to pick a class for the content';
}
You can not use $class since class is a keyword reserved.
This may work too:
$query = "INSERT INTO uploadedfiles (usename, filename, date, teacher, class) VALUES ($heyyou, $fname, $date, $prof, $class)";
Since double quote can understand variables when they inside it.
Another think is date is a keyword too reserved by MySQL.
Finlly try to see what $_POST['class']; content like this:
echo $_POST['class'];
Because perhaps you forget to give a name to your html element.
The variable $class is always set because of $class = $_POST['class']. so isset($class) will always be true regardless of class posted value. notice the difference in below statements:
$class = '';
if (isset($class)) {
echo 'a';
}
if($class) {
echo 'b';
}
the output is: a
//replace this:
if (!isset($class)){
echo 'You need to pick a class for the content'; }
else{
mysql_query($query);
}
//with this:
if (isset($class) && $class){
mysql_query($query);
else{
echo 'You need to pick a class for the content'; }
}