So, to start, I'm not exactly sure where to put this. It's something I've worked out for me and my assistant. I'm obsessed with security, to a degree, and I utilize .htaccess and .htpasswd to a high degree on my site.
Generally speaking, I use it to lock down the admincp and modcp (things that an attacker, who managed to high-jack an account, could do serious damage with).
So, this set of PHP files is essentially a dual/redundant password system. I've requested from my staff that they use a password that is DIFFERENT from their forum password to access the admin and moderator control panels.
So basically here we create a custom vbulletin page. I used Lynne's guide that I found
here
transcendence.php (forum root)
PHP Code:
<?php
// ####################### SET PHP ENVIRONMENT ###########################
error_reporting(E_ALL & ~E_NOTICE);
// #################### DEFINE IMPORTANT CONSTANTS #######################
define('THIS_SCRIPT', 'transcendence');
define('CSRF_PROTECTION', true);
// change this depending on your filename
// ################### PRE-CACHE TEMPLATES AND DATA ######################
// get special phrase groups
$phrasegroups = array();
// get special data templates from the datastore
$specialtemplates = array();
// pre-cache templates used by all actions
$globaltemplates = array('transcendence',
);
// pre-cache templates used by specific actions
$actiontemplates = array();
// ######################### REQUIRE BACK-END ############################
// if your page is outside of your normal vb forums directory, you should change directories by uncommenting the next line
// chdir ('/path/to/your/forums');
require_once('./global.php');
// #######################################################################
// ######################## START MAIN SCRIPT ############################
// #######################################################################
$navbits = construct_navbits(array('' => 'Expect Transcendence'));
$navbar = render_navbar_template($navbits);
// ###### YOUR CUSTOM CODE GOES HERE #####
$pagetitle = 'Expect Perfection - Expect Transcendence';
// ###### NOW YOUR TEMPLATE IS BEING RENDERED ######
$templater = vB_Template::create('transcendence');
$templater->register_page_templates();
$templater->register('navbar', $navbar);
$templater->register('pagetitle', $pagetitle);
print_output($templater->render());
?>
You'll then want to create your own template for it. I'm including mine as I've done quite a bit of work with it (to include the form that you submit with and allowing only certain usergroups to access it). Dave answered a
question I had in regards to vbulletin syntax and linked me to this
great list that I also feel like sharing!
The easiest way to edit all your templates is to go into debug mode (I have a password set for mine in my vbulletin config file). This allows you to edit the master style and add it to all of your styles at the same time. I'm sure there is an easier way as well and I also must warn you to not mess around with it too much. I accidentally deleted my postbit_legacy and have yet to find a way to get it back...haha... Good thing I don't use legacy...
Anyways, now the template!
transcendence (template)
PHP Code:
{vb:stylevar htmldoctype}
<html xmlns="http://www.w3.org/1999/xhtml" dir="{vb:stylevar textdirection}" lang="{vb:stylevar languagecode}" id="vbulletin_html">
<head>
<title>{vb:raw vboptions.bbtitle} - {vb:raw pagetitle}</title>
{vb:raw headinclude}
{vb:raw headinclude_bottom}
</head>
<body>
{vb:raw header}
{vb:raw navbar}
<div id="pagetitle">
<h1>{vb:raw pagetitle}</h1>
</div>
<h2 class="blockhead">Title</h2>
<div class="blockbody">
<div class="blockrow">
<vb:if condition="is_member_of($bbuserinfo, 6,7,26,27)">
<center>
<h1><B><U>Administrative Panel Access Form</u></b></h2>
<h4><I>Input your username. Then a password that IS NOT your current forum password (alpha numeric only, no special characters, at least 8 characters long)</i></h4><br/>
<i>Do note that your USERNAME and PASSWORD are case sensitive. I haven't set up validation on this form. Please don't screw around with it as it could damage the site. Thank you.<br/>
<hr/>
<form name="transcend" action="hash.php" method="post">
<table cellspacing="30">
<tr>
<th>Username</th>
<th>Password</th>
<th>Email</th>
</tr>
<tr>
<td><input type="text" name="username"/></td>
<td><input type="password" name="password"/></td>
<td><input type="text" name="email"/></td>
</tr>
<tr>
<td><input type="submit"/></td>
<td></td>
<td></td>
</tr>
</table>
</form>
<hr/><br/><br/>
</center>
</vb:if>
</div>
</div>
{vb:raw footer}
</body>
</html>
I made it so only my staff could actually see and use the form on this page by adding in this piece of code:
PHP Code:
<vb:if condition="is_member_of($bbuserinfo, 6,7,26,27)">
FORM HERE
</vb:if>
Just an added precaution.
The form has an action="hash.php"
This is the script I made to hash the password submitted by this form to the APR1 md5 format. It then appends this hashed password
automagically to your .htpasswd file (which should be outside of your public_html folder, I do mine one up from it (../.htpasswd))
This sounds dangerous right? If someone managed to get an admin/mod account...they could find this page and then simply give themselves a new admincp login, correct? That's why this form ALSO adds a # in front of the line, making it a comment meaning it can't be used until a server administrator opens up the .htpasswd file and removes the comment at the beginning!
This sounds like a bit of work, however, I promise you it's a lot less work then requesting that a staff member hash their password, give you the hashed password, you go into your .htaccess file and add it, and do this for all your staff members. It's easier to give them a link to the transcendence.php page and uncomment it once they finish.
The hash.php file goes a step further and also sends you an email whenever someone submits the form. It includes their username, their password, their email, and their IP address.
Now here is the hash.php script, it should be in your forum root with transcendence.php
hash.php (forum root)
PHP Code:
<?php
$ini = parse_ini_file("includes/config.ini.php");
// Create connection
function crypt_apr1_md5($plainpasswd)
{
$salt = substr(str_shuffle("abcdefghijklmnopqrstuvwxyz0123456789"), 0, 8);
$len = strlen($plainpasswd);
$text = $plainpasswd.'$apr1$'.$salt;
$bin = pack("H32", md5($plainpasswd.$salt.$plainpasswd));
for($i = $len; $i > 0; $i -= 16) { $text .= substr($bin, 0, min(16, $i)); }
for($i = $len; $i > 0; $i >>= 1) { $text .= ($i & 1) ? chr(0) : $plainpasswd{0}; }
$bin = pack("H32", md5($text));
for($i = 0; $i < 1000; $i++)
{
$new = ($i & 1) ? $plainpasswd : $bin;
if ($i % 3) $new .= $salt;
if ($i % 7) $new .= $plainpasswd;
$new .= ($i & 1) ? $bin : $plainpasswd;
$bin = pack("H32", md5($new));
}
for ($i = 0; $i < 5; $i++)
{
$k = $i + 6;
$j = $i + 12;
if ($j == 16) $j = 5;
$tmp = $bin[$i].$bin[$k].$bin[$j].$tmp;
}
$tmp = chr(0).chr(0).$bin[11].$tmp;
$tmp = strtr(strrev(substr(base64_encode($tmp), 2)),
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
return "$"."apr1"."$".$salt."$".$tmp;
}
$username = $_POST["username"]; /* Get USERNAME from FORM @ transcendence.php */
$password = $_POST["password"]; /* Get PASSWORD from FORM @ transcendence.php */
try {
$db = new PDO('mysql:host=localhost;dbName=' . $ini['databaseName'], $ini['databaseUsername'], $ini['databasePassword'], array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ));
$check = $db->prepare("SELECT * FROM " . $ini['databaseName'] . ".user WHERE username = ?");
$check->execute(array($username));
$result = $check->fetchAll();
foreach($result as $row) {
$vbPassword = $row['password'];
$vbSalt = $row['salt'];
$yourSubmittedPassword = md5(md5($password) . $vbSalt);
if ($vbPassword == $yourSubmittedPassword) {
echo("<center><h1>I'm sorry, your password cannot be the same as your vBulletin Password. Try again!</h1></center>");
$db = null;
die();
} else {
$hashedPassword = crypt_apr1_md5($password); /* Password is hashed using APR1 format */
$theHash = "# " . $username . ':' . $hashedPassword; /* Comments out the .htpasswd entry for approval */
file_put_contents($ini['htpasswdLocation'], $theHash.PHP_EOL, FILE_APPEND); /* Hashed PASSWORD is appended to the end of .htpasswd file */
$to = $ini['adminEmail'];
$subject = $ini['emailSubject'];
$headers = array();
$headers[] = "MIME-Version: 1.0";
$headers[] = "Content-type: text/plain; charset=iso-8859-1";
$headers[] = "From: Transcendence <" . $ini['adminEmail'] . ">";
$headers[] = "Reply-To: " . $_POST["username"] . " <" . $_POST["email"] . ">";
$headers[] = "Subject: {$subject}";
$headers[] = "X-Mailer: PHP/" . phpversion();
$message = "Greetings <B>" . $ini['administrator'] . "</B>,\r\n\r\nA user has requested access to the Administrative Control panel via the Transcendence page.\r\n\r\nThe Users Info is as Follows:\r\n\r\n<B>Username</B>: <I>" . $_POST["username"] . "</I>\r\n" . "<B>Email</b>: <I>" . $_POST["email"] . "</I>\r\n" . "<B>IP Address</B>: <I>" . $_SERVER['REMOTE_ADDR'] . "</I>\r\n" . "<B>Hashed Password</B>: <I>" . $theHash . "</I>\r\n\r\nConfirm with the User that they have requested this access. If they have, please go to the .htpasswd file and uncomment their hashed password to give them access. Thank you!\r\n\r\n\r\nTranscendence\r\n" . $ini['siteName'] . " at It's Finest";
$message = wordwrap($message, 70, "\r\n");
mail($to, $subject, $message, implode("\r\n", $headers));
echo("<center><h1>Thank You " . $username . "</h1><br/><br/>Your request has been submitted and an email has been sent to " . $ini['administrator'] . ".</center>");
}
}
$db = null;
} catch (PDOException $e) {
echo("Error: " . $e->getMessage() . "<br/>");
die();
}
?>
Rather then set all the variables in the hash.php file, I've made it so that it parses and config.ini.php file. You can edit most of the variables through it.
Here is the config.ini.php file, it should be in your includes folder. Make sure you call it config.ini.php, the php at the end is important as .ini files can be opened and read as plaintext.
config.ini.php (./forumRoot/includes)
PHP Code:
;<?php
;die(); /* DO NOT REMOVE THIS */
;/*
[DATABASE]
serverName = localhost
databaseUsername = yourDatabaseUsername
databasePassword = yourDatabasePassword
databaseName = YourDatabaseName
[BASIC INFORMATION]
administrator = yourNameHere
siteName = yourSiteNameHere
[HTPASSWD LOCATION]
htpasswdLocation = ../.htpasswd
[EMAIL INFORMATION]
emailSubject = Administrative Request
serverEmail = whoYouWantTheEmailToComeFrom
adminEmail = yourEmailHere
bccEmail = secondaryEmailAddressToBCC
bccName = nameOfPersonBeingBCC
;?>
Now any directory you want to password protect with .htaccess will require this in the directory:
.htaccess (per directory you want to protect)
Code:
AuthType Basic
AuthName "You Shall Not Pass"
AuthUserFile /path/to/.htpasswd
Require valid-user
You'll also want to decide where you wish to store your .htpasswd file, as stated, I keep mine 1 directory up from my forum. However, my forum is located at
www.mywebsitehere.com, if your forum is located at something along the lines of
www.mywebsitehere.com/forum/ you'll want to place your .htpasswd file at least 2 directories up (../../.htpasswd). It's important to keep it outside of your public_html/www folders.
That's pretty much it.
Things I'd like to do in the future
Form VerificationI'd like to add form validation in the template, however my javascript seems like it doesn't want to execute. I had this in the head section of my transcendence template:
HTML Code:
<script>
function validateForm() {
var x = document.forms["transcend"]["username"].value;
var y = document.forms["transcend"]["password"].value;
var z = document.forms["transcend"]["email"].value;
if (x == null || x == "" || y == null || y == "" || z == null || z == "") {
alert("All fields must be filled in");
return false;
}
}
</script>
[s]Check against current vBulletin password HashI'd like to write a script that compares the password they requested to their current vbulletin hash. This would, essentially, require me to hash the password they submit twice. First it would hash it in the format of that vBulletin 4 uses, then compare it to the one in the database. If it matches, it throws an error and refuses to let them use that password. If it doesn't match, it continues and rehashes their submitted password in the APR1 format. If anyone would like to help me with this, I'd really appreciate it![/s] Completed!
Well, I do hope this helped someone and I also really hope I put this in the correct place. I felt like contributing something that I found useful and helpful for managing my forum. Please let me know if I committed any sins against grammar/punctuation or if anything in my code is seriously flawed or dangerous.
Much appreciated!