I'd like to know why OWASP ZAP detect potential SQL injection on my login page. I call an API to connect my users.
PHP slim API code:
$sql = "SELECT id, idGroup, idTeam,lastName, firstName, isLogged, login, phoneNumber, webrtc FROM users WHERE enable = 1 AND login = :login AND password = :password";
$db = new db();
$db = $db->connect();
$stmt = $db->prepare($sql);
$userPass = md5($password);
$stmt->bindParam(':login', $login);
$stmt->bindParam(':password', $userPass);
$stmt->execute();
$user = $stmt->fetchAll(PDO::FETCH_OBJ);
Login page:
$login = $_POST['username'];
$password = $_POST['password'];
$client = new GuzzleHttp\Client();
$response = $client->request('POST', $apiUrl . 'agent/login', [
'form_params' => [
'login' => $login,
'password' => $password,
'ipAddress' => $_SERVER['REMOTE_ADDR'],
]
]);
$data = json_decode($response->getBody(), true);
if (isset($data[0]['id']) && $data[0]['id'] > 0) {
$_SESSION['fullName'] = $data[0]['firstName'] . ' ' . $data[0]['lastName'];
$_SESSION['idGroup'] = $data[0]['idGroup'];
$_SESSION['idTeam'] = $data[0]['idTeam'];
$_SESSION['idUser'] = $data[0]['id'];
$_SESSION['login'] = $data[0]['login'];
$_SESSION['phoneNumber'] = $data[0]['phoneNumber'];
$_SESSION['webrtc'] = $data[0]['webrtc'];
//Get roles for user
$response = $client->request('GET', $apiUrl . 'web/permissions/' . $login);
$data = json_decode($response->getBody(),true);
foreach ($data as $roles) {
$_SESSION['roles'][$roles['bit']] = $roles['name'];
}
echo "<script>window.open('index.php','_self')</script>";
}
All my APIs use prepared statements and parameterized queries.
Here's the OWASP ZAP alert:
The page results were successfully manipulated using the boolean
conditions [ZAP" AND "1"="1" -- ] and [ZAP" AND "1"="2" -- ] The
parameter value being modified was NOT stripped from the HTML output
for the purposes of the comparison Data was returned for the original
parameter.
This may happen if the response page for the form submission contains the value of a form field as it was specified by a user. For instance if you are logging in your user and use the value of 'username' field to greet the user but pull it not from the DB but from the request variables. SQL injection does not take place but the scanning script assumes that you stored the value unsanitized in the DB while you just using the value provided by a user and not the value that you have stored in the DB. Hope this makes sense.
Related
I'm trying to post new user signup to the data using getParam, I used RestClient to test if value is retrieved or not turns out to be null every time:
this is the json data I put in the body, and I do add a content type header based on application/json:
{
"email" : "test#test.com",
"username" : "test",
"password" : "123",
}
This is the code for posting user (note that var_dump($email) and die I used it just to check $email does contain a value or not, will be removed when it is fixed):
//add new user
$app->post('/user/add', function(Request $request, Response $response){
$email = $request->getParam('email');
$username = $request->getParam('username');
$password = $request->getParam('password');
$sql = "INSERT INTO users (email, username, password) VALUES (?, ?, ?)";
var_dump($email);
die ("end");
$conn = new Database();
$conn = $conn->createConnection();
$stmt = $conn->prepare($sql);
$stmt->bind_param('sss', $email, $username, $password);
$check = $stmt->execute();
$error = $stmt->error;
if ($check){
echo 'Added Successful';
}
if ($error != ''){
echo $error;
}
});
I did the same thing for posting appoinment by /calendar/add and it work prefectly, bot post method are the same but why post user is not getting the value?
getParam() is only used for query parameters like: yoururl.com?param1=value1
You have to use
$body = $request->getParsedBody();
echo $body['email'];
instead
DZDomi's answer is not quite accurate because getParam() of Request class does call getParsedBody() method internally. It should work with query strings or data from POST body.
If you get NULL when sending POST request, make sure that you setup client to use application/x-www-form-urlencoded or multipart/form-data.
I do not know about RestClient, but if you use Postman, you can define this information on Body tab on Postman UI.
I have built a log in system and I am adding google recaptcha for security. I am getting an error on this line: $result = json_decode($url, TRUE);
The error says;
failed to open stream: HTTP request failed! HTTP/1.0 400 Bad Request.
This is my first time using recaptcha and I am not sure if this is a common mistake.
<?php
$secret = '*****';
$response = $_POST['g-recaptcha-response'];
$remoteip = $_SERVER['REMOTE_ADDR'];
$captcha = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=$secret&response=$response&remoteip=$remoteip");
$result = json_decode($url, TRUE);
$username;
$password;
$captcha;
if (isset($_POST['username']))
$username = $_POST['username'];
if (isset($_POST['password']))
$password = $_POST['password'];
if (isset($_POST['g-recaptcha-response']))
$captcha = $_POST['g-recaptcha-response'];
if (!$captcha) {
echo '<p class="error-message">Please Complete The Captcha!</p>';
header("location: login.php");
exit;
}
$response = json_decode(file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=6LfG-S8UAAAAAIqW1sBE31yMPyO4zeqOCgDzL1mA&response=" . $captcha . "&remote=" . $_SERVER['REMOTE_ADDR']), true);
if ($response['success'] == false) {
echo '<p class="error-message">Please Fill Captcha!</p>';
} else {
echo '<p class="error-message2">Welcome</p>';
}
if (isset($_POST['submit'])) {
$username = $_POST['username'];
$password = $_POST['password'];
$q = $handler->prepare('SELECT * FROM users WHERE username = ?');
$q->execute(array($username));
$result = $q->fetch(PDO::FETCH_ASSOC);
if ($result !== false) {
$hash_pwd = $result['password'];
$hash = password_verify($password, $hash_pwd);
if ($hash) {
$_SESSION['username'] = $username;
header("location:index.php");
return;
} else {
echo '<p class="error-message3"><br><br>You have ented an incorrect login!<br>Please try again</p>';
}
}
}
?>
If this really is your complete code:
It seems you are using $url (in the line $result = ...) without having initialized it before.
Additionally, I would expect that a variable with name $url contains an URL, and URLs are not in JSON format, so this raises some alarm signs. You eventually do not want to JSON-parse an URL, but instead parse what this URL returns when calling it.
Secondly, sometimes the line numbers within error messages or warnings are misleading. I highly doubt that the error you have mentioned (HTTP request failed) is related to json_decode(). json_decode(), as the name implies, just parses a string in JSON format, but does not load anything via HTTP.
So the error message probably comes from the line above ($captcha = file_get_contents(...);). I suppose that the URL you give there is wrong, or that Google refuses the request for another reason.
The first thing I would do is putting that URL into a variable and print it out (e.g. by using error_log()).
If that does not lead to the source of the problem, I would copy that URL (not from the code, but from the output produced by error_log()) and paste it directly into the address bar of a new browser window. If this yields the expected result (you should see Google's answer to the request in the browser window), the error is in your code. Otherwise, the error is in the URL.
<?php
session_start();
error_reporting(E_ALL);
try {
$ini = parse_ini_file("/var/www/admin.ini");
$user = $ini['user'];
$pass = $ini['pass'];
$name = $ini['name'];
$host = $ini['host'];
$handler = new PDO('mysql:host='.$host.'; dbname='.$name, $user, $pass);
$handler->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $e){
error_log($e);
echo $e->getMessage();
}
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => 'https://www.google.com/recaptcha/api/siteverify',
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => [
'secret' => '********',
'response' => $_POST['g-recaptcha-response'],
],
]);
$response = json_decode(curl_exec($curl));
if (!$response->success) {
if (isset($_POST['submit'])) {
$username = $_POST['username'];
$password = $_POST['password'];
$q = $handler->prepare('SELECT * FROM users WHERE username = ?');
$q->execute(array($username));
$result = $q -> fetch(PDO::FETCH_ASSOC);
if ($result !== false) {
$hash_pwd = $result['password'];
$hash = password_verify($password, $hash_pwd);
if ($hash) {
$_SESSION['username'] = $username;
header("location:index.php");return;
}
else {echo '<p class="error-message3"><br><br>You have ented an incorrect login!<br>Please try again</p>';
}
}
}
}
?>
I'm having trouble getting the Authentication to work with laravel 4. This is my whole sign in function when a user enters their email and password into the form.
public function getSignin() {
$return_arr = array();
$email = Input::get('email');
$password = Input::get('password');
$validation = Validator::make(
array(
'Email' => $email,
'Password' => $password
), array(
'Email' => 'required|Email',
'Password' => 'required'
)
);
if ($validation->passes()) {
$pass = base64_encode($password);
$details = array ('email' => $email, 'password' => $pass);
if (Auth::attempt($details)) {
$return_arr['frm_check'] = 'success';
$return_arr['msg'] = 'logged in';
} else {
$return_arr['frm_check'] = 'error';
$return_arr['msg'] = 'log in failed';
}
} else {
$errors = $validation->messages();
$return_arr['frm_check'] = 'error';
$return_arr['msg'] = $errors->first();
}
echo json_encode($return_arr);
$this->layout = null;
return;
}
Even though the email and password are in the same row in the database, it still returns log in failed, was wondering if anyone could shed some light on to this situation?
If I've missed off any other crucial details let me know and I'll post them right away. Thanks in advance.
Based on your comments...
When you're creating your $user, use Hash::make($password) to hash the password using BCrypt, before saving it in your db.
Then, when the user's logging in just use Auth::attempt($credentials) as you are, but don't use base_64 to encrypt it, the Auth method does it all for you!
Much more on the excellent Laravel docs: http://laravel.com/docs/security
Unless you have base64 encoded your password on save(), remove this line from your code:
$pass = base64_encode($password);
And edit this one to:
$details = array ('email' => $email, 'password' => $password);
Auth::attempt() will hash it for you, using something safer than base64.
EDIT:
To correctly save your passwords you have to do something like this:
$user = new User;
$user->email = 'me#me.com';
$user->password = Hash::make('mySuperSecretPassword');
$user->save();
Then you can user attempt just passing it unhashed.
Here's a tutorial I wrote; which might help!
https://medium.com/on-coding/e8d93c9ce0e2
So I've been up all night trying to figure this out (literally). But Im stumped. What I want to do is, simply, to save access tokens for future use and to allow users to not have to "Allow" the app each time. When I use the access tokens in "done.php" that I stored and retrieved, the "GET" action works but the "POST" doesnt.
addblogs.php (This script, ran right after registration, snags the access tokens and stores them... right now it isn't salted, but it will be)
include('functions.php');
require_once('tumblroauth/tumblroauth.php');
require_once('config.php');
session_start();
sec_session_start();
$tumblrblog = $_SESSION['tumblrblog'];
$connection = new TumblrOAuth(CONSUMER_KEY, CONSUMER_SECRET, $_SESSION['oauth_token'], $_SESSION['oauth_token_secret']);
$access_token = $connection->getAccessToken($_REQUEST['oauth_verifier']);
unset($_SESSION['oauth_token']);
unset($_SESSION['oauth_token_secret']);
if (200 == $connection->http_code) {
$at = implode(",",$access_token);
$insert_stmt = $mysqli->prepare("INSERT INTO tumblogs (tumblrblog, access_token) VALUES ( ?, ?)");
$insert_stmt->bind_param('ss', $tumblrblog, $at);
$insert_stmt->execute();
print_r ($access_token);
} else {
header('Location: ./clearsessions.php');
}
done.php (Retreive and Use the saved access tokens)
include('functions.php');
session_start();
sec_session_start();
require_once('tumblroauth/tumblroauth.php');
require_once('config.php');
$tumblrblog = $_SESSION['tumblrblog'];
$stmt = $mysqli->prepare('SELECT access_token FROM `tumblogs` WHERE tumblrblog=? LIMIT 1');
$stmt->bind_param("s", $tumblrblog);
$stmt->execute();
$stmt->bind_result($at);
$stmt->fetch();
$access_token = explode(",", $at);
$connection = new TumblrOAuth(CONSUMER_KEY, CONSUMER_SECRET, $access_token['0'], $access_token['1']);
print_r($access_token['0']);
$hostname = "$tumblrblog.tumblr.com";
$connection = new TumblrOAuth(CONSUMER_KEY, CONSUMER_SECRET, $access_token['0'], $access_token['1']);
$userinfo = $connection->get('http://api.tumblr.com/v2/user/info');
print_r($userinfo);
$pic_path = "dir/$tumblrblog/good/icon.png";
$pic = file_get_contents($pic_path);
$connection->post('api.tumblr.com/v2/blog/$hostname/post', array('type' => 'text', 'body' => 'this is a test of Tumbloadr v2'));
Thank you all for taking a look!
Brandon
In done.php I would verify that you're also retrieving the correct access token secret by printing $access_token[1] as well. Also, I would remove one of the $connections, theres no need to do it twice. To be a bit more dynamic, you can get the $hostname using this
$hostname = parse_url($blog->url,PHP_URL_HOST);
This clearly isn't a fix, but hopefully it helps a bit.
Why not serialize accessToken before inserting to database? I think it will be the correct way to store tokens in database. You can never know if the token contains "," chars then would your explode destroy the whole access token.
When I store tokens for Dropbox I had to serialize and unserialize when using it.
I am developing a component for Joomla. It has integrations with popular social websites. I retrieve user information from database via given social profile. Then, I try to make this user login with the following code:
$fbuser = $facebook->api(
'/me',
'GET',
array(
'access_token' => $_SESSION['active']['access_token']
)
);
// Get a database object
$db =& JFactory::getDBO();
$query = "SELECT * FROM #__users WHERE email = '".$fbuser['email']."';";
$db->setQuery($query);
$row = $db->loadRow();
if(isset($row))
{
$app = JFactory::getApplication();
$user =& JUser::getInstance($row[0]);
$credentials = array();
$credentials['username'] = $user->get('username');
$credentials['password'] = $user->get('password'); // When I change this to related users plain password then it works
$options = array();
$options['remember'] = true;
$options['silent'] = true;
$app->login($credentials, $options);
}
else
{
return 'There is no account associated with facebook';
}
The problem is database return encoded password and this doesn't work. When I give decoded password to $credentials it works. What can be the problem?
One option is to create your own authentication plugin (quite simple task) that would log in any user with a specific password known only to you and the site.
Then you can supply that password along with known username.
For the sake of security, only allow that plugin to log in ordinary users, and not admins.
You need to MD5 hash the pwd (the way it's stored in the DB).
try this:
$salt = '19IQYkelXrqVH1Eht6PFOIZRe5T1SQHs';
$pwd = md5_hex($pwd . $salt) .":$salt";
$query = "select name,username,email,password from jos_users where password = $pwd;";
...
// --- login mamanam.com
$app = JFactory::getApplication();
$credentials = array();
$credentials['username'] = $username;
$credentials['password'] = $password;
$app->login($credentials);
Necessary parameters in array $credentials=array() for logon function Joomla! $app->login($credentials)
*Sorry my English is not so good