Reviving 20 year old web forum software

(This is a guest post by xorhash.)

What makes you nostalgic? I don’t know about you, but for me, it’s definitely early 2000s web forums. Names like vBulletin, UltimateBB, phpBB, YaBB, IkonBoard, … bring a smile to my face. Thus, I figured it would be time to revisit the oldest vBulletin I could get my hands on. As it turns out, vBulletin used to offer “vBulletin Lite” back in the year 2000, which is a version of vBulletin 1.x stripped down so much, it almost stops being vBulletin.

Because they hid it behind a form, the web archive didn’t quite catch it, but I managed to find a different copy online, which seems pristine enough at least: vbulletinlite101.zip

So that’s just a bunch of code. I could just get a period-appropriate Red Hat 9 installation going, but that’d be boring. How much work could it possibly be to get this to run? In hindsight: just about six hours. Please allow me to say that the code is of rather questionable quality. Do not expose this to the Internet. Without even trying, I found at least two SQL injections. Every SQL injection immediately leads to code execution under PHP as well since the templates are interpreted using eval(). And so I set out on my quest to port this to a modern OS.

SoftwareOriginal RequirementMy Version
Operating System“different flavours of UNIX, as well as Windows NT/98”Ubuntu 19.04
InterpreterPHP 3.0.9PHP 7.2.19
DatabaseMySQL 3.22MariaDB 10.3.13

The details of this are rather boring, so allow me to point out some highlights and discoveries made while digging through the code:

  • 50 reply limit: Threads were limited to 50 replies. There was no pagination. Any replies beyond that would just replace the most recent post. I’m not sure if this was an attempt at preventing server and client load from excessively large pages or an attempt to “encourage” people to actually buy vBulletin.
  • No accounts: Unlike vBulletin 1.x, there were no accounts. All posts would just have a username field and an optional field for an e-mail address; even if provided, the e-mail address does not get verified.
  • No thread/post management: There’s no way to conveniently delete threads or posts, leaving the forums completely defenseless against spam. I suspect this was by design, so that nobody would stick with vBulletin Lite.
  • Icon plagiarism: The icons for the “search” and “home” buttons are actually taken from Internet Explorer 4. For comparison, here are the buttons in Internet Explorer:
Internet Explorer 4 search button Internet Explorer 4 home button
  • Questionable security: vBulletin Lite was not a pinnacle of secure and defensive coding. Though some efforts were made (e. g. using addslashes(), which is nowadays considered inappropriate, but was all that what was available at the time in PHP 3), they were not thorough and overlooked spots. When encountering a database error, the actual SQL query and error details would be shown in an HTML comment on the error page, greatly helping attackers build their SQL injection even without source code available. The admin control panel password is stored in plaintext: on the server as well as in the cookie that persists an admin session. I’m also not sold on using eval() for interpreting templates from the database.
  • Filenames ending in .php3: Back then, it was common for PHP scripts to have a filename ending in .php3, though I couldn’t find the exact reason why this used to be common practice (possibly to allow PHP/FI 2.0 and PHP 3.0 to co-exist, maybe?). Nowadays, everything’s normally just a .php file.
  • register_globals was very much a thing: The PHP (anti-)feature register_globals caused request parameters and cookies to be turned into global variables in the script, e. g. https://www.php.example/test.php?x=1 would set $x to 1. vBulletin Lite relied on register_globals existing and working. PHP removed it in version 5.4, so a lot of request handling needed to be changed for vBulletin Lite to work at all.
  • MySQL has implicit defaults: Apparently, if strict mode is not enabled, MySQL has implicit defaults for various data types. vBulletin Lite relied on this behavior, much to my surprise. I’m not sure who thought this was a good feature, but it sure surprised me.
  • Password caching until exactly 2020: When successfully logging into the admin control panel, a cookie “controlpassword” is set. It is hardcoded to expire at the beginning of 2020—next year. I’m glad I didn’t have to try and debug that subtle issue. My patch makes it so that the cookie expires at the start of the next year.
  • A typo in the admin control panel: In admin/forum.php, deletion of a forum should bring the list of forums again. However, due to a typo (“modfiy” instead of “modify”), the page instead stays blank. I also took the liberty to fix this obvious bug.
  • Feature remnants: vBulletin Lite kind of looks like a rushjob; I’d love to find out if that’s true. There are leftovers of various features, which manifest themselves in stray variables being referenced but never set. For example, the e-mail field in the template for the newthread.php page actually references $password, which nothing else ever reads or sets. Similarly, forumdisplay.php references a $datecut variable, which I assume regular vBulletin 1.x would use to prune old threads by date (to save space on the database?).
  • Ampersands in HTML: vBulletin had literal ampersands (&) in the templates, namely in links. Firefox complains about this nowadays and expects &amp; even in <a href>, but I didn’t want to touch that because I’m afraid I might break an old browser by changing this behavior.

As mentioned above, I made a patch for vBulletin Lite 1.0.1 to make it work with modern versions of PHP and MySQL: vbulletinlite101-2019.diff
Applying it requires some preparation (renaming the files from .php3 to .php and adjusting the names of included files ahead of time); after that, it should apply cleanly:

$ for i in *.php3; do mv $i $(basename $i .php3).php; done
$ cd admin && for i in *.php3; do mv $i $(basename $i .php3).php; done
$ cd .. && find . -name "*.php" -exec sed -i 's/php3/php/g' {} \;
$ patch -p1 < PATH_TO_PATCH.diff

vBulletin Lite had a mechanism that would send e-mail a configurable address about SQL errors. I ended up disabling that in db_mysql.php, spilling the error onto the page and kept that behavior in the patch to make debugging easier (since this has no business running in production anymore anyway). See the areas marked with TODO if you want to undo that after all.

I used the new ?? syntax introduced in PHP 7, so this patch may not immediately work with PHP 5, though the worst grunt work has already been taken care of.

And for those who want to give it a kick, I put one up on vbulletin.virtuallyfun.com.


The website that used to host vBulletin Lite notes that “vBulletin Lite may be modified for your own use only. Under no circumstances may any modified vBulletin Lite code be distributed”.

I hope that separating this into a pristine archive and a patch—with no functional changes—is good enough. Should this still not be enough for the rightsholders (currently MH Sub I, LLC dba vBulletin), takedown requests will of course be honored.

Author: neozeed

I live in SE Asia, doing generic work, enjoying my life & family

12 thoughts on “Reviving 20 year old web forum software”

  1. From what I remember, *.phpx extensions came from web hosting companies. At that time more than one version of PHP were installed and the http server had rules based on extension to execute files with the good interpreter . 🙂

  2. As a kid I loved bulletin boards, especially WoltLab’s Burning Board with its distinct blue color. Amazing to see that vBulletin Lite was such a mess. Thanks & respect for bringin it back to the internet.

    PS.: I tried to post this to the board itself, but..:
    mysql error: Field ‘threadindex’ doesn’t have a default value
    mysql error number: 1364

      1. It… should be working, actually. I made sure to change the admin/install.php in the patch:

        @@ -205,10 +207,10 @@
        description CHAR(250) NOT NULL,
        active SMALLINT NOT NULL,
        displayorder SMALLINT NOT NULL,
        – replycount INT UNSIGNED NOT NULL,
        – lastpost INT NOT NULL,
        – threadcount MEDIUMINT UNSIGNED NOT NULL,
        – allowposting SMALLINT NOT NULL,
        + replycount INT UNSIGNED NOT NULL DEFAULT 0,
        + lastpost INT NOT NULL DEFAULT 0,
        + threadcount MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
        + allowposting SMALLINT NOT NULL DEFAULT 0,
        PRIMARY KEY(forumid)
        )”);

        Are you sure you applied the patch before installing?

        1. @@ -247,12 +249,12 @@
          title CHAR(100) NOT NULL,
          lastpost INT UNSIGNED NOT NULL,
          forumid SMALLINT UNSIGNED NOT NULL,
          – replycount INT UNSIGNED NOT NULL,
          + replycount INT UNSIGNED NOT NULL DEFAULT 0,
          postusername CHAR(50) NOT NULL,
          dateline INT UNSIGNED NOT NULL,
          – subjectindex CHAR(100) NOT NULL,
          – threadindex MEDIUMTEXT NOT NULL,
          – userindex MEDIUMTEXT NOT NULL,
          + subjectindex CHAR(100) NOT NULL DEFAULT ”,
          + threadindex MEDIUMTEXT NOT NULL DEFAULT ”,
          + userindex MEDIUMTEXT NOT NULL DEFAULT ”,
          PRIMARY KEY(threadid)

          is the chunk, actually. My bad. Still in the patch, so I’m mildly confused.

          1. This disposable $1 a month VM is older, but I ended up purging MySQL 5.7 and loading Maria 10.0 … This time building the tables went through without any error, and I can post.

        2. The patch applied fine, the MySQL didn’t like the idea of it being ”.. I’ll reapply and post the exact message soon. Sorry the last 2 days have been a bit crazy..

          1. Fucking what. Apparently, MySQL is rather picky about this and I didn’t notice because MariaDB:

            https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html

            > The BLOB, TEXT, GEOMETRY, and JSON data types can be assigned a default value only if the value is written as an expression, even if the expression value is a literal: […]

            Well, that clears that up. Guess we’re MariaDB-only now. I might get around to a MySQL patch one day(TM).

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.