6
\$\begingroup\$

This project is ultimately my senior thesis to graduate from college. It will end up being very long with several code reviews and redesigns. The next rubberduck perhaps?

I've been working on a project that tests awareness of phishing scams. It generates emails based on templates and sends them out to a database of emails if they haven't received a "project" of that nature recently.

This utilizes a very in-depth GUI to send out emails based on inputs provided by a user, an interface to view and track the results generated off of webbugs placed in the emails and on associated webpages, a design view to create new email templates (currently, you must input HTML and eventually plain text is inputted and parsed as HTML), and links to other relevant pages that are associated with the company using the product and information about the project.

The entire project can be found here on GitHub.

Ultimately, I'm looking to address these four major points:

  1. Security
  2. Encapsulation
  3. Cleanliness
  4. Readability

This means that I ultimately need to take my God Class that I used to create a demoable product to show people, and separate it out into several working classes while keeping the root controller intact to function as the logic class of the application.

To make this more readable, I've separated it down to the methods within the class and made each it's own code segment. Something also worth mentioning is that I may end up rewriting this into another language to be used as a learning experience. With that said, what would be some strong languages to use that are relevant in today's world that this application would be effective in? With all of that said, here we go:

PhishingController

Webbug execution - email and website:

public function webbugExecutionWebsite($urlid) {
    $db = $this->openDatabaseDefault();
    if(!empty($_SERVER['REMOTE_ADDR'])) {
        $ip = $_SERVER['REMOTE_ADDR'];
        $host = gethostbyaddr($_SERVER['REMOTE_ADDR']);
        $reqpath = $_SERVER['REQUEST_URI'];
        $projectID = substr($reqpath,16);
        $projectID = ltrim($projectID,'0');
        $projectID = intval(strval($projectID));
        $sql = "SELECT PRJ_ProjectName FROM gaig_users.projects WHERE PRJ_ProjectId=$projectID;";
        $projectNameResult = $db->query($sql);
        $project = $projectNameResult->fetch_assoc();
        $projectName = $project['PRJ_ProjectName'];
        $browseragent = $_SERVER['HTTP_USER_AGENT'];
        $date = date("Y-m-d");
        $time = date("H:i:s");
        $sql = "SELECT USR_Username FROM gaig_users.users WHERE USR_UniqueURLId='$urlid';";
        $userNameResult = $db->query($sql);
        $user = $userNameResult->fetch_assoc();
        $username = $user['USR_Username'];
        $sql = "INSERT INTO gaig_users.website_tracking (WBS_Id,WBS_Ip,WBS_Host,
    WBS_BrowserAgent,WBS_ReqPath,WBS_Username,WBS_ProjectName,WBS_AccessDate,WBS_AccessTime) VALUES 
    (null,'$ip','$host','$browseragent','$reqpath','$username','$projectName','$date',
    '$time');";
        $result = $db->query($sql);
    }
    $db->close();
}

public function webbugExecutionEmail($urlid) {
    $db = $this->openDatabaseDefault();
    if(!empty($_SERVER['REMOTE_ADDR'])) {
        $ip = $_SERVER['REMOTE_ADDR'];
        $host = gethostbyaddr($_SERVER['REMOTE_ADDR']);
        $reqpath = $_SERVER['REQUEST_URI'];
        $projectID = substr($reqpath,29);
        $projectID = ltrim($projectID,'0');
        $projectID = rtrim($projectID,'.');
        $projectID = intval(strval($projectID));
        $sql = "SELECT PRJ_ProjectName FROM gaig_users.projects WHERE PRJ_ProjectId=$projectID;";
        $projectNameResult = $db->query($sql);
        $project = $projectNameResult->fetch_assoc();
        $projectName = $project['PRJ_ProjectName'];
        $sql = "SELECT USR_Username FROM gaig_users.users WHERE USR_UniqueURLId='$urlid';";
        $userNameResult = $db->query($sql);
        $user = $userNameResult->fetch_assoc();
        $username = $user['USR_Username'];
        $date = date("Y-m-d");
        $time = date("H:i:s");
        $sql = "INSERT INTO gaig_users.email_tracking (EML_Id,EML_Ip,EML_Host,EML_Username,EML_ProjectName,
    EML_AccessDate,EML_AccessTime) VALUES (null,'$ip','$host','$username','$projectName','$date','$time');";
        $result = $db->query($sql);
    }
    $db->close();
}

Database connection creator:

public function openDatabaseDefault() {
    $db = new mysqli(getenv('DB_HOST'), getenv('DB_USERNAME'), getenv('DB_PASSWORD'), getenv('DB_DATABASE'));
    date_default_timezone_set('America/New_York');
    if (mysqli_connect_errno()) {
        echo 'Error: Could not connect to the database.';
        echo "Errno: " . $db->connect_errno . "\n";
        echo "Error: " . $db->connect_error . "\n";
        exit;
    }
    return $db;
}

Random ID generator for unique URLs:

private function random_str($length, $keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') {
    $str = '';
    $max = mb_strlen($keyspace, '8bit') - 1;
    for ($i = 0; $i < $length; ++$i) {
        $str .= $keyspace[random_int(0, $max)];
    }
    return $str;
}

Sending out emails:

public function sendEmail(Request $request) {
    $fromEmail = $request['fromEmail'];
    $fromPass = $request['fromPass'];
    $host = $request['hostName'];
    $port = $request['port'];
    putenv("MAIL_HOST=$host");
    putenv("MAIL_PORT=$port");
    putenv("MAIL_USERNAME=$fromEmail");
    putenv("MAIL_PASSWORD=$fromPass");
    $subject = $request['subject'];
    $projectName = $request['projectName'];
    $projectId = substr($projectName,strpos($projectName,'_'));
    $projectName = substr($projectName,0,strpos($projectName,'_')-1);
    $companyName = $request['companyName'];
    $emailTemplate = 'emails.' . $request['emailTemplate'];
    $emailTemplateType = substr($request['emailTemplate'],0,3);
    $emailTemplateTarget = substr($request['emailTemplate'],3,1);
    //$fromEmail = 'gaigemailtest@gmail.com';
    //$subject = 'URGENT: Corporate Account Breach - Read Immediately';
    $db = $this->openDatabaseDefault();
    $sql = "SELECT * FROM gaig_users.users;";
    if(!$result = $db->query($sql)) {
        $this->databaseErrorLogging($sql,$db);
        exit;
    }
    if($result->num_rows === 0) {
        echo "Sorry. There are no users in this database.";
        exit;
    }
    while($user = $result->fetch_assoc()) {
        if($emailTemplateType != substr($user['USR_ProjectMostRecent'],-5,3) || $emailTemplateTarget != substr($user['USR_ProjectMostRecent'],-2,1)) {
            $urlID = null;
            if(!is_null($user['USR_UniqueURLId'])) {
                $urlID = $user['USR_UniqueURLId'];
            }
            while(is_null($urlID)) {
                $urlID = $this->random_str(15);
                $sql = "SELECT * FROM gaig_users.users WHERE USR_UniqueURLId=$urlID;";
                $tempResult = $db->query($sql);
                //if($tempResult->num_rows === 0) {
                //  break;
                //}
                //$urlID = null;
            }
            $username = $user['USR_Username'];
            $toEmail = $user['USR_Email'];
            $lastName = $user['USR_LastName'];
            $firstName = $user['USR_FirstName'];
            //$projectName = 'bscG6_9_16';
            /*
             * NAMING FORMAT:
             * 1. bsc/adv : First three letters defines whether its basic or advanced scam
             * 2. G/T : This letter defines whether it's a generic scam or a targeted scam
             * 3. Project Start Date
             */
            $headers = array('from' => $fromEmail, 'to' => $toEmail, 'subject' => $subject, 'lastName' => $lastName,
                'urlID' => $urlID, 'username' => $username, 'projectName' => $projectName, 'companyName' => $companyName,
                'firstName' => $firstName, 'projectId' => $projectId);
            Mail::send(['html' => $emailTemplate],$headers, function($m) use ($fromEmail, $toEmail, $subject) {
                $m->from($fromEmail);
                $m->to($toEmail)->subject($subject);
            });
            if(!is_null($user['USR_UniqueURLId'])) {
                $project_mostRecent = $user['USR_ProjectMostRecent'];
                $project_previous = $user['USR_ProjectPrevious'];
                $sql = "UPDATE gaig_users.users SET USR_ProjectMostRecent='$projectName-$emailTemplate', USR_ProjectPrevious='$project_mostRecent', USR_ProjectLast='$project_previous' WHERE USR_Username='$username';";
                $updateResult = $db->query($sql);
            }
            else {
                $sql = "UPDATE gaig_users.users SET USR_UniqueURLId='$urlID', USR_ProjectMostRecent='$projectName-$emailTemplate' WHERE USR_Username='$username';";
                $updateResult = $db->query($sql);
            }
            echo "Mail sent to " . $toEmail;
            echo "Unique URL ID generated: " . $urlID . "<br />";
        } else {
            echo "Mail not sent to " . $user['USR_Username'] . "@gaig.com";
            echo "User's last project was " . $user['USR_ProjectMostRecent'] . "<br />";
        }
    }
    $db->close();
}

View all templates:

public function viewAllTemplates() {
    $userId = \Session::get('authUserId');
    if($userId) {
        $files = [];
        $fileNames = [];
        $filePrefaces = [];
        $fileTypes = [];
        $filesInFolder = \File::files('../resources/views/emails');
        foreach($filesInFolder as $path) {
            $files[] = pathinfo($path);
        }
        $templateSize = sizeof($files);
        for($i = 0; $i < $templateSize; $i++) {
            $fileNames[$i] = $files[$i]['filename'];
            $fileNames[$i] = substr($fileNames[$i],0,-6);
            $filePrefaces[$i] = substr($fileNames[$i],0,3);
            $fileTypes[$i] = substr($fileNames[$i],3,1);
            if($fileTypes[$i] == 'T') {
                $fileTypes[$i] = 'tar';
            } else if($fileTypes[$i] == 'G') {
                $fileTypes[$i] = 'gen';
            } else {
                $fileTypes[$i] = 'edu';
            }
        }
        $varToPass = array('templateSize'=>$templateSize,'fileNames'=>$fileNames,'filePrefaces'=>$filePrefaces,'fileTypes'=>$fileTypes);
        return view('displays.showAllTemplates')->with($varToPass);
    } else {
        \Session::put('loginRedirect',$_SERVER['REQUEST_URI']);
        return view('auth.loginTest');
    }
}

View all projects (projects that track a group of emails):

public function viewAllProjects() {
    $userId = \Session::get('authUserId');
    if($userId) {
        $db = $this->openDatabaseDefault();
        $sql = "SELECT PRJ_ProjectId, PRJ_ProjectName, PRJ_ProjectStatus FROM gaig_users.projects;";
        if(!$projects = $db->query($sql)) {
            $this->databaseErrorLogging($sql,$db);
            exit;
        }
        if($projects->num_rows === 0) {
            echo "Sorry. There are no users in this database.";
            exit;
        }
        $project = $projects->fetch_all();
        $data = array();
        $projectSize = sizeof($project);
        for($i = 0; $i < $projectSize; $i++) {
            $data[$i] = array('PRJ_ProjectId'=>$project[$i][0],'PRJ_ProjectName'=>$project[$i][1],'PRJ_ProjectStatus'=>$project[$i][2]);
        }
        $varToPass = array('projectSize'=>$projectSize,'data'=>$data);
        $db->close();
        return view('displays.showAllProjects')->with($varToPass);
    } else {
        \Session::put('loginRedirect',$_SERVER['REQUEST_URI']);
        return view('auth.loginTest');
    }
}

Create new:

public function createNewProject(Request $request) {
    $projectName = $request->input('projectNameText');
    $projectAssignee = $request->input('projectAssigneeText');
    $date = date("Y-m-d");
    $db = $this->openDatabaseDefault();
    $sql = "INSERT INTO gaig_users.projects (PRJ_ProjectId,PRJ_ProjectName,PRJ_ProjectAssignee,PRJ_ProjectStart,
        PRJ_ProjectLastActive,PRJ_ProjectStatus,PRJ_ProjectTotalUsers,PRJ_EmailViews,PRJ_WebsiteViews,
        PRJ_ProjectTotalReports) VALUES (null,'$projectName','$projectAssignee','$date','$date','Inactive',0,0,0,0);";
    if(!$projects = $db->query($sql)) {
        $this->databaseErrorLogging($sql,$db);
        exit;
    }
    $db->close();
}
public function createNewTemplate(Request $request) {
    $path = '../resources/views/emails/';
    $templateName = $request->input('templateName');
    $path = $path . $templateName . '.blade.php';
    $templateContent = $request->input('templateContent');
    \File::put($path,$templateContent);
    \File::delete('../resources/views/emails/.blade.php');
}

HTML returner to display templates via AJAX to user in real-time:

public function htmlReturner($id) {
    $path = '../resources/views/emails/' . $id . '.blade.php';
    $contents = '';
    try {
        $contents = \File::get($path);
    }
    catch (FileNotFoundException $fnfe) {
        $contents = "Preview Unavailable";
    }
    return $contents;
}

Storing default settings for forms:

public function updateDefaultEmailSettings(Request $request) {
    $username = $request['usernameText'];
    $company = $request['companyText'];
    $host = $request['mailServerText'];
    $port = $request['mailPortText'];
    $userId = \Session::get('authUserId');
    if($userId) {
        $db = $this->openDatabaseDefault();
        $checkExists = "SELECT DFT_UserId FROM gaig_users.default_emailsettings WHERE DFT_UserId='$userId';";
        if(!$checkExistsResult = $db->query($checkExists)) {
            $this->databaseErrorLogging($checkExists,$db);
            exit;
        }
        if($checkExistsResult->num_rows === 0) {
            $insert = "INSERT INTO gaig_users.default_emailsettings (DFT_UserId, DFT_MailServer, DFT_MailPort,
                    DFT_Username, DFT_CompanyName) VALUES ('$userId','$host','$port','$username',
                    '$company');";
            $insertResult = $db->query($insert);
            exit;
        } else {
            $update = "UPDATE gaig_users.default_emailsettings SET DFT_MailServer='$host', DFT_MailPort='$port',
                    DFT_Username='$username', DFT_CompanyName='$company';";
            $updateResult = $db->query($update);
        }
        $db->close();
    } else {
        return view('auth.loginTest');
    }
}

Authentication:

public function postLogin(Request $request) {
    $username = $request['usernameText'];
    $password = $request['passwordText'];
    $db = $this->openDatabaseDefault();
    $selectHash = "SELECT USR_Password,USR_UserId FROM gaig_users.users WHERE USR_Username='$username';";
    if(!$hashResults = $db->query($selectHash)) {
        $this->databaseErrorLogging($selectHash,$db);
        exit;
    }
    if($hashResults->num_rows === 0) {
        $varToPass = array('errors'=>array("We failed to find the username provided. Check your spelling and try 
            again. If this problem continues, contact your manager."));
        return view('auth.loginTest')->with($varToPass);
    }
    $hashResult = $hashResults->fetch_assoc();
    $db->close();
    if(password_verify($password,$hashResult['USR_Password'])) {
        \Session::put('authUser',$username);
        \Session::put('authUserId',$hashResult['USR_UserId']);
    } else {
        $varToPass = array('errors'=>array('The password provided does not match our records.'));
        return view('auth.loginTest')->with($varToPass);
    }
    $redirectPage = \Session::get('loginRedirect');
    if($redirectPage) {
        return redirect()->to($redirectPage);
    } else {
        return view('errors.500');
    }
}
public function postRegister(Request $request) {
    $username = $request['usernameText'];
    $password = $request['passwordText'];
    $firstName = $request['firstNameText'];
    $lastName = $request['lastNameText'];
    $password = password_hash($password,PASSWORD_DEFAULT);
    $db = $this->openDatabaseDefault();
    $insertUser = "INSERT INTO gaig_users.users (USR_UserId,USR_Username,USR_FirstName,USR_LastName,
            USR_UniqueURLId,USR_Password,USR_ProjectMostRecent,USR_ProjectPrevious,USR_ProjectLast) VALUES
            (null,'$username','$firstName','$lastName',null,'$password',null,null,null);";
    $insertResult = $db->query($insertUser);
    $selectUserId = "SELECT USR_UserId FROM gaig_users.users WHERE USR_Username='$username';";
    $selectResults = $db->query($selectUserId);
    $selectResult = $selectResults->fetch_assoc();
    $db->close();
    \Session::put('authUser',$username);
    \Session::put('authUserId',$selectResult['USR_UserId']);
}
public function logout() {
    \Session::forget('authUser');
    \Session::forget('authUserId');
    \Session::forget('loginRedirect');
    return redirect()->to('http://localhost:8888');
}
public function isUserAuth() {
    $return = array('authCheck'=>\Session::get('authUserId'));
    return $return;
}

JSON pages for displaying tracking in real-time:

public function postWebsiteJson() {
    $userId = \Session::get('authUserId');
    if($userId) {
        $websiteData = array();
        $websiteSelect = "SELECT WBS_Ip,WBS_Host,WBS_ReqPath,WBS_Username,WBS_ProjectName,WBS_AccessDate,
                    WBS_AccessTime FROM gaig_users.website_tracking;";
        $db = $this->openDatabaseDefault();
        if(!$websiteResults = $db->query($websiteSelect)) {
            $this->databaseErrorLogging($websiteSelect,$db);
            exit;
        }
        if($websiteResults->num_rows === 0) {
            //echo "Sorry. There are no users in this database.";
            //exit;
        }
        $websiteResult = $websiteResults->fetch_all();
        for($i = 0; $i < sizeof($websiteResult); $i++) {
            $websiteData[$i] = array('WBS_Ip'=>$websiteResult[$i][0],'WBS_Host'=>$websiteResult[$i][1],
                'WBS_ReqPath'=>$websiteResult[$i][2],'WBS_Username'=>$websiteResult[$i][3],
                'WBS_ProjectName'=>$websiteResult[$i][4],'WBS_AccessDate'=>$websiteResult[$i][5],
                'WBS_AccessTime'=>$websiteResult[$i][6]);
        }
        $db->close();
        return $websiteData;
    }
}
public function postEmailJson() {
    $userId = \Session::get('authUserId');
    if($userId) {
        $emailData = array();
        $emailSelect = "SELECT EML_Ip,EML_Host,EML_Username,EML_ProjectName,EML_AccessDate,
            EML_AccessTime FROM gaig_users.email_tracking;";
        $db = $this->openDatabaseDefault();
        if(!$emailResults = $db->query($emailSelect)) {
            $this->databaseErrorLogging($emailSelect,$db);
            exit;
        }
        if($emailResults->num_rows === 0) {
            //echo "Sorry. There are no users in this database.";
            //exit;
        }
        $emailResult = $emailResults->fetch_all();
        for($i = 0; $i < sizeof($emailResult); $i++) {
            $emailData[$i] = array('EML_Ip'=>$emailResult[$i][0],'EML_Host'=>$emailResult[$i][1],
                'EML_Username'=>$emailResult[$i][2],'EML_ProjectName'=>$emailResult[$i][3],
                'EML_AccessDate'=>$emailResult[$i][4],'EML_AccessTime'=>$emailResult[$i][5]);
        }
        $db->close();
        return $emailData;
    }
}
public function postReportsJson() {
    $userId = \Session::get('authUserId');
    if($userId) {
        $reportData = array();
        $reportsSelect = "SELECT RPT_EmailSubject,RPT_UserEmail,RPT_OriginalFrom,RPT_ReportDate FROM gaig_users.reports;";
        $db = $this->openDatabaseDefault();
        if(!$reportsResults = $db->query($reportsSelect)) {
            $this->databaseErrorLogging($reportsSelect,$db);
            exit;
        }
        if($reportsResults->num_rows === 0) {
            //echo "Sorry. There are no users in this database.";
            //exit;
        }
        $reportsResult = $reportsResults->fetch_all();
        for($i = 0; $i < sizeof($reportsResult); $i++) {
            $reportData[$i] = array('RPT_EmailSubject'=>$reportsResult[$i][0],'RPT_UserEmail'=>$reportsResult[$i][1],
                'RPT_OriginalFrom'=>$reportsResult[$i][2],'RPT_ReportDate'=>$reportsResult[$i][3]);
        }
        $db->close();
        return $reportData;
    }
}

Error logging for database - ultimately creating an Errors table to hold this data:

public function databaseErrorLogging($sql,$db) {
    echo "Sorry, the website is experiencing technical difficulties.";
    echo "Error: Our query failed to execute and here is why: \n";
    echo "Hash Select Query: " . $sql . "\n";
    echo "Errno: " . $db->errno . "\n";
    echo "Error: " . $db->error . "\n";
}

These are all the methods that I found completely relevant. There are several more methods in this God class that generate the forms for the GUI. Eventually I'll get to code review on these.

\$\endgroup\$
  • 2
    \$\begingroup\$ I'd recommending restoring the original title since it was more relevant to the code's purpose. \$\endgroup\$ – Jamal Jun 30 '16 at 23:35
  • \$\begingroup\$ Welcome to Code Review! I have rolled back the last edit. Please see what you may and may not do after receiving answers. \$\endgroup\$ – Pimgd Jul 1 '16 at 13:13
5
\$\begingroup\$

Security

Try to follow current best practices. If you are very new to web security, take a quick look at the OWASP Top 10.

SQL Injection

You are open to SQL Injection via the $urlid parameter as well as $browseragent, $reqpath, and possibly $ip and $host. As well as do second order injection via $username and $projectName. And that's just in your first two functions, your other functions are vulnerable as well.

It should also be said that casting to int or escaping input is not the best approach to SQL injections.

Current best-practice is to use prepared statements. It's safer, it's more readable, it's easy to use. There is really no excuse to not use them.

XSS

You echo variables without encoding, which can lead to XSS. Examples are $toEmail, $user['USR_Username'], etc.

Ideally, you should use a templating engine, which encodes variables by default. This is more secure, and also results in better code.

Structure / Encapsulation / Readability

Try to read up on MVC, and try to make more use of Laravel functionality. For example, Laravel has a query builder, templates, etc.

Globals

From a quick first look at your code, there is a lot of work being done on global variables, either via superglobals or via setenv.

Duplication

webbugExecutionWebsite and webbugExecutionEmail contain a lot of duplicate code (they are essentially the same function). Try to extract that duplication to functions which you then reuse.

Your functions do too much

Your functions do way too much, which is why you have so much duplication. And why your code is quite difficult to read, or change, or test.

A good example is sendEmail which indeed sends mail, but also proccesses a request, does some work on projects and templates, logs stuff, selects users from the database, and updates users in the database, exists, and prints information to the user.

None of your functions should ever exit, and only your functions that are explicitly meant to print something should print something.

Passing God Objects

Functions should accept the specific arguments they need, not some god request object. So instead of this:

public function updateDefaultEmailSettings(Request $request) {
    $username = $request['usernameText'];
    $company = $request['companyText'];
    $host = $request['mailServerText'];
    $port = $request['mailPortText'];
    $userId = \Session::get('authUserId');

write this:

public function updateDefaultEmailSettings($username, $company, $host, $port, $userId) {

Now it's explicit what this function needs, and you can even use it outside of a specific request (eg when testing).

It does accept quite a lot of arguments, but if you want to reduce the size, you need to do it right: Combine variables that logically belong together. Eg host and port could be part of a URL class.

Formatting / Cleanliness

Your formatting and code cleanliness is pretty good. Your code is mostly consistent and well formatted, your variable names are understandable, etc. A couple of small points:

  • You mix between snake_case and camelCase (and sometimes just nocase, eg urlid). Choose one and stick with it.
  • Your variable names are sometimes not consistent. For example the request usernameText vs username variable vs USR_Username database field. Here it's just the pre-/post-fix that is different, but another example is authUserId vs USR_UserId vs authCheck for the same value. Try to name the same things the same to avoid confusion.
\$\endgroup\$
  • \$\begingroup\$ In regards to SQL Injection, what would be the best way to structure this? I wasn't using Stored Procedures and Functions because ideally I want to be able to allow the user to create custom reporting (i.e. I want to see xyz) and then a query is generated to pull the information requested. \$\endgroup\$ – Trojan404 Jul 1 '16 at 12:06
  • \$\begingroup\$ It's worth mentioning that this entire project is inaccessible without authentication, so would protecting the login page from SQL Injection be enough? \$\endgroup\$ – Trojan404 Jul 1 '16 at 12:07
  • \$\begingroup\$ @throck95: Your current queries aren't about custom reporting, so I don't think it matters for the code you posted. All your current queries could be secure, so there is no reason that they shouldn't be. But either way, it should most definitely be possible to create custom reporting without giving the user complete access to your database. \$\endgroup\$ – tim Jul 1 '16 at 12:22
  • \$\begingroup\$ @throck95: No, just protecting the login is not good enough. What if there is an authentication bypass somewhere? What if you later want to allow access to someone, but don't want that someone to have complete control of your database, and possibly server? Apart from the practical side, I would be very uncomfortable handing in a thesis that contains obviously vulnerable code. \$\endgroup\$ – tim Jul 1 '16 at 12:22
  • 1
    \$\begingroup\$ @throck95 Also, I reverted your edit, see What you may and may not do after receiving answers \$\endgroup\$ – tim Jul 1 '16 at 13:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.