PHP Developers Network

A community of PHP developers offering assistance, advice, discussion, and friendship.
 
Loading
It is currently Sun Apr 30, 2017 7:56 am

All times are UTC - 5 hours




Post new topic Reply to topic  [ 5 posts ] 
Author Message
 Post subject: How's my PHP?
PostPosted: Fri Sep 02, 2016 8:03 pm 
Offline
Forum Newbie

Joined: Fri Sep 02, 2016 7:41 pm
Posts: 4
Syntax: [ Download ] [ Hide ]
<?php
/*
Why bother with PDO?

Database specs:
user varchar(32), hash character(60), merit bigint,

--Todos--
Is it good?

Note: bcrypt causes passwords to be truncated to 72 chars.

Functions and output:

1) conSql
        0 Unable to connect to database
        (sql connection)
2) rmUser
        0 Success
        1 Permission denied
        2 Unable to connect to DB
        3 Unknown error
3) authUser
        0 Inauthentic
        1 Authentic
4) unlogUser
        (none)
5) logUser
        0 Success
        1 Incorrect username/password
        2 Unable to connect to DB
6) chkUser_exist
        0 Doesn't exist
        1 exists
        2 Unable to connect to DB
7) makeUser
        0 Success
        1 Permission denied
        2 User already exists
        3 Unable to connect to database
        4 Unknown error
8) chkPermit
        0 Permission denied
        1 Permission granted
*/


define("CONF","Conf/Shar/kernel/"); //Helps make checking permissions much easier

//A bunch of SQL constants, makes changing info easier
define("hostname","localhost");
define("username","user");
define("password","lCvebzHkf3mN1ERwfLewVWx8i2LjhLrF");
define("database","db");

function conSql()
        {
        $sqlcon = mysqli_connect(hostname, username, password, database);
        if(!$sqlcon)
                {
                echo "Fatal: Shar/kernel.php, Unable to connect to database";
                return 0;
                }
        return $sqlcon;
        }

function rmUser($merits,$user)
        {
        if(chkPermit($merits,CONF."rmUser") == 0){return 1;}
        $sqlcon = conSql();
        if(!$sqlcon){return 2;}
        $e_user = mysqli_real_escape_string($sqlcon,$user);
        $query  = "DELETE FROM users WHERE user='$e_user'";
        if(mysqli_query($sqlcon, $query)){return 0;}
        echo "Fatal: Shar/kernel.php, rmUser, SQL error.";
        return 3;
        }

function authUser()
        {
        if(!isset($_SESSION["user"])){return 0;}
        $sqlcon = conSql();
        if(!$sqlcon){return 0;}
        $e_user = mysqli_real_escape_string($sqlcon,$_SESSION["user"]);
        $query = "SELECT * FROM users WHERE user='$e_user'";
        $result = mysqli_query($sqlcon, $query);
        while($row = mysqli_fetch_assoc($result))
                {
                if(
                ($row["user"] == $_SESSION["user"]) and
                ($row["hash"] == $_SESSION["hash"])   )
                        {return 1;}
                }
        return 0;
        }

function logoutUser(){
        $_SESSION["user"] = "";
        $_SESSION["hash"] = "";
        session_destroy();
        }

function loginUser($user,$key)
        {
        $sqlcon = conSql();
        if(!$sqlcon){return 2;}
        $e_user = mysqli_real_escape_string($sqlcon,$user);
        $query = "SELECT * FROM users WHERE user='$e_user'";
        $result = mysqli_query($sqlcon, $query);
        while($row = mysqli_fetch_assoc($result))
                {
                if($row["user"] == $user)
                        {
                        if(password_verify($key,$row["hash"]) == 1)
                                {
                                session_start();
                                $_SESSION["user"] = $row["user"];
                                $_SESSION["hash"] = $row["hash"];
                                $_SESSION["merit"]=$row["merit"];
                                return 0;
                                }
                        }
                }
        return 1;
        }

function chkUser_exist($user) //is this correct? Can this function be shortened?
        {
        $sqlcon = conSql();
        if(!$sqlcon){return 2;}
        $e_user = mysqli_real_escape_string($sqlcon,$user);
        $query = "SELECT user FROM users WHERE user='$e_user'";
        $result = mysqli_query($sqlcon, $query);
        while($row = mysqli_fetch_assoc($result))
                {
                if($row["user"] == $user){mysqli_close($sqlcon);return 1;}
                }
        mysqli_close($sqlcon);
        return 0;
        }//The function works, but can it be better somehow?

function makeUser($merits, $user, $key)
        {
        if(chkPermit($merits,CONF."makeUser") == 0){return 1;} //Do we have permission?
        if(chkUser_exist($user)){return 2;} //Does the user already exist?
        $hash = password_hash($key,PASSWORD_BCRYPT); //create the hash
        $sqlcon = conSql(); //connect to the db
        if(!$sqlcon){return 3;}
        $e_user = mysqli_real_escape_string($sqlcon,$user); //secure the data
        $e_hash = mysqli_real_escape_string($sqlcon,$hash);
        $e_merit= mysqli_real_escape_string($sqlcon,  "0");
        $query = "INSERT INTO users VALUES ('$e_user','$e_hash','$e_merit')"; //create the query
        if(mysqli_query($sqlcon, $query)){mysqli_close($sqlcon);return 0;}
        echo "Fatal: Shar/kernel.php, makeUser, SQL error";
        mysqli_close($sqlcon);
        return 4;
        }

function chkPermit($merits,$entity)
        {
        if(!file_exists($entity))
                {
                echo 'Fatal: Shar/kernel.php, chkPermit, $entity = '.$entity.', file not found';
                return 0;
                }
        $level = file_get_contents($entity);
        if($merits >= $level){return 1;}
        else{return 0;}
        }
?>
 


Okay, story time! So, I was on a chat I frequent, and some guy came on, pestered the admin over some small amount of HTML code that he didn't likem, and started attacking us. He saw this code I posted, and then made fun of me. It's clearb that this guy was a troll, but damn what he said was really burned in my mind. He particularly focused on the "genSalt" function, and said that I can't even indent properly. Okay, now to relevant info (some of the stuff he said was rediculous), he said that the function was stupid and wrong, because it meant that Apache has access to the urandom device, which was supposedly a huge security flaw. Honestly, I feel like ranting, if you want the story, please ask. He also said that I should've used the random_bytes() function instead of the function I used. He said the whole thing was terrible, but made no other specifications than that.

EDIT:
I have updated the code, how is it now? I kind of understand exceptions much better, but I don't see how it's better than just using error codes. It seems much simpler and easier, not to mention they seem to work better. I honestly can't handle PDO and its object orientation. I'm not going to, use anything other than mysql anyways. I like the idea of prepared statements, but I don't see where in my code they'd work better than escape strings. I may use them in the future though. What do you guys think? The particular functions I'm concerned about it chkUser and loginUser.


Last edited by Giganitris on Sat Oct 08, 2016 8:54 pm, edited 2 times in total.

Top
 Profile  
 
PostPosted: Fri Sep 02, 2016 9:16 pm 
Offline
Spammer :|
User avatar

Joined: Wed Oct 15, 2008 2:35 am
Posts: 6398
Location: WA, USA
Giganitris wrote:
because it meant that Apache has access to the urandom device, which was supposedly a huge security flaw.

:D Well, that's completely wrong. I hope he's a troll because otherwise it would mean he's stupid. Could be both.

General comments:

- Never connect to the database as root. Even with a secure password. Always create a user dedicated to whatever purpose you need, and only the minimum privileges necessary on only the databases required. So basically SELECT, INSERT, and UPDATE for all tables, then DELETE for your thing but restricted to the users table.
- Avoid deleting stuff from the database. "Deactivating" or "hiding" is almost always better, partly for record-keeping and partly for security.
- Don't use return codes for functions. If I call rmUser and it returns 3, what does that mean? I have to hunt around for an answer. Don't simulate true/false with 1/0 either - it's unnecessarily confusing. (a) Throw exceptions for each circumstance, like DatabaseConnectionException (extends DatabaseException) and DatabaseQueryFailureException (ditto), or (b) return true or false and log the error somewhere accessible. Exceptions are better.
- Avoid abbreviations and acronyms unless the full meaning is well known in the industry (eg, within your company or in the PHP development world) and it legitimately saves you time and effort. "rm" is well known but isn't saving you much over "delete", and "auth" is well known and does save you from having to type "authenticate" every time, but on the other hand "chk" really should be spelled out. Readability is far, far more important than using fewer strokes on a keyboard.
- Do not expose MySQL error messages. They can contain information about your queries and database structure that a malicious user could use to attack your application. Display a generic message and log the original message someplace safe. Ditto for other situations where error messages can contain sensitive information.
- There's a good chance you're not familiar with prepared statements. Learn about them and what they're for. Some people insist on using prepared statements for everything because it virtually eliminates the possibility of SQL injection, but a knowledgeable developer can use mysqli_real_escape_string just fine.
- Consolidate your database connection code into one place. If you find yourself copy/pasting code into more than a couple locations, or creating *shudder* "snippets", then it needs to be refactored.
- Learn how to do password hashing correctly. (1) Do not generate your own salts. (2) Don't store the salt separately from the password because there's no need to. (3) The final "password" hash will contain both the hashing algorithm, the salt, and the hashed value. This is fine. Don't touch it and just store it as-is. It won't contain any binary data either so bin<->hex conversions are unnecessary - and actually detrimental. (4) When verifying a password, get the hash value from the database and use password_verify() to validate it; the only time you ever call password_hash() is to generate the password hash you're going to store.

Specific comments on your code:

- User functions: Doing a "SELECT * FROM users" will query for and return every single record in the table. Using PHP code to sift through that looking for one record is horrifically inefficient and wasteful. God kills a puppy and kitten every time someone does that. Instead, construct a specific SELECT query with the conditions already inside it. For example, after you've fixed the password stuff such that there's only the one password hash column in the table, then
Code:
SELECT * FROM users WHERE user = '{$e_user}' AND merit = '{$e_merit}'

Remember that you're using PHP to check the password, so there shouldn't be anything in the query about a hash you've calculated (not the least bit because there's no way for you to do so at that point).
- logUser: Sounds like it's logging something about a user. This problem goes back to avoiding abbreviations. Call it the full "logInUser".
- logUser: If the session is empty but already started then session_start() will raise a warning. Putting aside the fact that there are better methods for checking if the session has started than by looking in $_SESSION, starting the session should be done at a global level that is not directly tied to some specific action. There's no problem with always starting a session on every page and simply not using it if you don't need its information.
- genSalt: This function should not exist at all.
- chkPermit: Why are these level/merit things stored as numbers in files? This sounds really bad. It should very likely be stored in the database instead.

mysqli vs. PDO? Either. Both have advantages and disadvantages over the other. As most people are never going to change database systems, it comes down to which API you prefer.


Top
 Profile  
 
PostPosted: Fri Sep 02, 2016 10:13 pm 
Offline
Forum Newbie

Joined: Fri Sep 02, 2016 7:41 pm
Posts: 4
Wow, that's a lot of valuable information. I guess I have a lot of re-writing to do. Also, the "merits" aren't stored in a file, but in the db. The rw file that it pulls the info from is the merits needed to do a thing. I know it's silly, it's kind of for fun. I was trying to simulate Unix permission thingies, I guess.

About return codes for functions, why not? The intended thing is that I write a documentation file that has the return codes at the very beginning, and it makes it much easier for a dev to write a file that includes mine. Or even if I provide a table in the comments, wouldn't that also work? I was kind of following the "Rule of Repair: Repair what you can — but when you must fail, fail noisily and as soon as possible." As you can see, I'm more of a C/C++ guy. I'm also obsessed with Unix philosophy.

What are the harmful things an SQL error can contain?

Haha I'm sorry about the "snippets" thing. This is the only file that'll contain SQL at all.

How do I separate the salt that's stored with the hash to verify the password, assuming I have to do it myself.?

A friend recommended PDO over mysqli, so I guess I might change it to that and rewrite my SQL to use prepared statements.


Also just a snippet of what the troll said "Apache2 is the most pwned bloatware of them all. You really are a skiddy."
"You can't even indent the code properly."

Please tell me I'm not a skiddy, that's the worst insult in all of software history. Seriously, I've been called a lot of horrible things, but that hurts the most.


Top
 Profile  
 
PostPosted: Fri Sep 02, 2016 11:14 pm 
Offline
Spammer :|
User avatar

Joined: Wed Oct 15, 2008 2:35 am
Posts: 6398
Location: WA, USA
Ah, a C guy. That explains so much.

Having programming experience is great, but don't take that knowledge and try to fit it into the PHP paradigm. Square peg, round hole. Learning PHP is about more than just syntax and function names - you have to learn how to write PHP code too. How the language works, what it can and cannot do, and the best way(s) it provides to accomplish a goal. The knowledge you should be carrying over is about code theory, not code implementation.

C doesn't really have any good mechanism for indicating various forms of failure. No exceptions, no classes. You call a function, it returns a value (and maybe has a few pointers for output parameters). That's all you have to work with. Actually, to be more precise, that's the C paradigm: you could use structs, or arrays, or another structure, but it cuts into efficiency and performance. And C is big on efficiency.

PHP, being a higher-level language, is not like that. Efficiency is good, of course, but the most important aspect of PHP code is its maintainability; over-optimization will hurt you in the long run. It has exceptions you can use to interrupt program flow for "exceptional" circumstances, and it has classes where you can bundle together data and behavior into a single cohesive unit. The PHP paradigm is about writing descriptive and hopefully self-documenting code.

As for the Unix philosophy, that's cool and all but remember that it's not programming philosophy. There are certain beneficial aspects like its separation of concerns, but not everything will be helpful with PHP.

Here's what I suggest: learn about the nature of PHP - like, go through the reference section of the manual. Learn about classes, interfaces, extensions, INI settings, exceptions, databases, resources... all the tools available to you as a PHP developer. Then consider what those tools can do to help you accomplish a goal. You need to switch your mentality from "how would I write this in C and translate it into PHP" to "how would I write this natively in PHP".

Giganitris wrote:
About return codes for functions, why not? The intended thing is that I write a documentation file that has the return codes at the very beginning, and it makes it much easier for a dev to write a file that includes mine. Or even if I provide a table in the comments, wouldn't that also work?

That's the C mentality. Set that aside.

What does PHP provide that is relevant here? The best answer is booleans and exceptions. If your function should return a simple yes or no for success then it can return true/false; if your function should generally work but sometimes there may be a problem then it can throw an exception. Let's set aside the "best way" to use exceptions and just stick with the base Exception class.

Take rmUser, which we'll now call "deleteUser" (remember: descriptive and self-documenting). For the most part it will delete a user successfully, but there are multiple circumstances where it might not succeed. As such the exception strategy is more appropriate than true/false.

In pseudocode:
Code:
function deleteUser($merits, $user) {
   if user cannot delete then throw an Exception("Permission denied");

   $sqlcon = singleFunctionToGetDatabaseConnection();
   if attempt to delete user fails then throw an Exception("Failed to delete user");

   // if we're here then everything was successful
   // we don't need anything here to indicate success because that's obvious from a lack of thrown exceptions
}

Since we're using exceptions, the poorly-named "singleFunctionToGetDatabaseConnection" (too descriptive) could throw its own exception if it fails to connect - that's another example of "should generally work" code.

Exceptions require a bit more code to work their best: try/catch blocks.
Code:
try {
   deleteUser($merits, $user);
   indicate success
} catch (Exception $e) {
   indicate failure // exception message is $e->getMessage()
}

Without a try/catch any exception that is thrown will cause the script to abort; to borrow C terminology, exceptions are like longjumps to the closest try/catch block, and no block in the call stack means a crash.

Does that make sense?


Giganitris wrote:
What are the harmful things an SQL error can contain?

If there is a syntax error in a MySQL query, like
Code:
DELETE FROM users WHER user='$e_user'

then the error message will look like
Quote:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'WHER user='Giganitris'' at line 1

That is, it contains a portion of the query itself starting at the location of the syntax error. For this query it's not much of a problem, but if you put password hashes or secret keys or other information that needs to be kept from the user, they may be able to see it in the error message.

Giganitris wrote:
Haha I'm sorry about the "snippets" thing. This is the only file that'll contain SQL at all.

It's a pet peeve. Many people have a "snippets" or "shared code" file with stuff they've written and rewritten over the years, available so they can reuse it without having to write it yet again. Personally, I think it promotes copy and paste programming, which is something I really hate.

Giganitris wrote:
How do I separate the salt that's stored with the hash to verify the password, assuming I have to do it myself.?

You don't. It's one of the things that PHP does almost uniquely from every other language out there: hashes contain all the information necessary.

Consider a simple MD5 hash on a salted password. The algorithm itself will spit out a 128-bit string, which most people (including PHP) represent as a 32-hex-character string. That's the whole hash. To replicate that hash you need to know the original input and the salt... and the algorithm. Normally you would store the hash and salt separately, and simply program MD5 into your application, but PHP can combine them all together into one string in the form
Code:
$1$salt........hash............................

or more generically
Code:
'$' algorithm-identifier '$' specific-information-for-the-algorithm hash


Here, MD5 is represented by the identifier "1", the specific information is a salt is 12 characters long (other algorithms need more than just a salt), and the hash is 32 characters long.
(Why 12 for the salt? I don't know. But if you want to use the built-in mechanisms for this unified string thing then the salt needs to be that long. Which is fine.)

The most obvious benefit with this methods is that there is only one piece of data to manage, but there's another: you can use this exact string to duplicate a hash as well, as the string itself functions as a "salt" with the $1$ indicating the algorithm and only the first however-many characters (here, 12) being used for the actual salt to the algorithm.

What I've described is specific to crypt and password_hash. You can always "roll your own" using other functions.

Giganitris wrote:
A friend recommended PDO over mysqli, so I guess I might change it to that and rewrite my SQL to use prepared statements.

Good.

Giganitris wrote:
Also just a snippet of what the troll said "Apache2 is the most pwned bloatware of them all. You really are a skiddy."
"You can't even indent the code properly."

Please tell me I'm not a skiddy, that's the worst insult in all of software history. Seriously, I've been called a lot of horrible things, but that hurts the most.

He's a troll. Don't feed the troll.

You're not a skiddy. Your issue is not that you don't know what you're doing, but that you know what you're doing in one language and trying to apply it to another. On top of that you're still learning PHP. Obviously you're not going to be best coder around - what matters is how you proceed from here.


Top
 Profile  
 
PostPosted: Sat Sep 03, 2016 2:06 am 
Offline
Forum Newbie

Joined: Fri Sep 02, 2016 7:41 pm
Posts: 4
Thank you, that's very helpful. I think that answers all my questions.

EDIT:
I have updated the code, how is it now? I kind of understand exceptions much better, but I don't see how it's better than just using error codes. It seems much simpler and easier, not to mention they seem to work better. I honestly can't handle PDO and its object orientation. I'm not going to, use anything other than mysql anyways. I like the idea of prepared statements, but I don't see where in my code they'd work better than escape strings. I may use them in the future though. What do you guys think? The particular functions I'm concerned about it chkUser and loginUser. Can this be done more efficiently?


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 5 posts ] 

All times are UTC - 5 hours


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Jump to:  
Powered by phpBB® Forum Software © phpBB Group