mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 03:49:26 +00:00
feat(ruleset): PHP quick-mode rules from the engine's vocabulary, measured on the development corpus
Quick mode had no PHP rules, so a PHP quick scan read the files and reported a plausible zero. ruleset/php/rules.toml adds 13 line/statement rules built on semantic/php.toml's sinks, sources and sanitizers (sql/wpdb, command, code_eval, unserialize, echo, file_inclusion, filesystem, outbound_request, redirect). 9 are enabled and 4 are shadow (sql-call-composition, code-eval, unserialize, xss-echo-input). A rule is shadow when it fired on the corpus but never at a known site. precision_estimate is the measured lower bound. Measured in-sample on the development corpus only (pmpro introduce/repair/benign, population-v1 PHP cases; population-v2 was not read). Known site hit on the vulnerable pin in 4 of 5 cases (LearnPress missed). The hit disappears on the fixed pin for pmpro and YesWiki. Numbers, per-pin files/bytes read and the engine comparison are in benchmarks/measurements/2026-09-30-php-quick-rules/. - fixtures php-vulnerable / php-benign plus manifests: ousast benchmark covers PHP (9/14 reported, 13/14 including shadow; the benign twin records 2 known false alerts) - PHP `#` comments are comments; `#[...]` attributes are not - the alerts task now reports PHP as quick coverage; alerts-engine keeps running the engine for PHP next to the quick rules (ENGINE_ALONGSIDE_QUICK) - other languages' benchmark findings and metrics are unchanged Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
45d02ecf20
commit
7927d6e539
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
// Benign twin of benchmarks/fixtures/php-vulnerable/app.php: the same handlers, each one repaired the way its
|
||||
// upstream fix did it. Every quick-mode finding here is a false alert (benchmarks/manifests/php-benign.toml).
|
||||
|
||||
$conn = mysqli_connect('localhost', 'app', 'app', 'app');
|
||||
|
||||
function order_by_code($code)
|
||||
{
|
||||
global $wpdb;
|
||||
return $wpdb->get_var($wpdb->prepare("SELECT id FROM {$wpdb->prefix}orders WHERE code = %s LIMIT 1", $code));
|
||||
}
|
||||
|
||||
function find_user($conn)
|
||||
{
|
||||
$stmt = mysqli_prepare($conn, 'SELECT * FROM users WHERE name = ?');
|
||||
mysqli_stmt_bind_param($stmt, 's', $_GET['name']);
|
||||
mysqli_stmt_execute($stmt);
|
||||
return mysqli_stmt_get_result($stmt);
|
||||
}
|
||||
|
||||
function members_sorted($order)
|
||||
{
|
||||
$order = in_array(strtoupper($order), array('ASC', 'DESC'), true) ? strtoupper($order) : 'ASC';
|
||||
return ' ORDER BY u.user_login ' . ('DESC' === $order ? 'DESC' : 'ASC') . ' ';
|
||||
}
|
||||
|
||||
function create_form($data)
|
||||
{
|
||||
return 'INSERT INTO forms (id, label) VALUES (' . intval($data['id']) . ', "x")';
|
||||
}
|
||||
|
||||
function captcha_cleanup($db, $userAgent)
|
||||
{
|
||||
$delete = sprintf(
|
||||
"DELETE FROM captcha WHERE useragent = '%s'",
|
||||
$db->escape($userAgent)
|
||||
);
|
||||
$db->query($delete);
|
||||
}
|
||||
|
||||
function ping()
|
||||
{
|
||||
system('ping -c 1 ' . escapeshellarg($_GET['host']));
|
||||
}
|
||||
|
||||
function calculate($formula)
|
||||
{
|
||||
return is_numeric($formula) ? (float) $formula : 0.0;
|
||||
}
|
||||
|
||||
function restore_session()
|
||||
{
|
||||
return json_decode($_COOKIE['session'], true);
|
||||
}
|
||||
|
||||
function greet()
|
||||
{
|
||||
echo 'Hello ' . htmlspecialchars($_GET['name'], ENT_QUOTES);
|
||||
}
|
||||
|
||||
function page()
|
||||
{
|
||||
$pages = array('home', 'about');
|
||||
include in_array($_GET['page'], $pages, true) ? $_GET['page'] . '.php' : 'home.php';
|
||||
}
|
||||
|
||||
function download()
|
||||
{
|
||||
readfile('/var/www/files/' . basename($_GET['file']));
|
||||
}
|
||||
|
||||
function fetch_preview()
|
||||
{
|
||||
$url = filter_var($_GET['url'], FILTER_VALIDATE_URL);
|
||||
$ch = curl_init(wp_http_validate_url($url) ? $url : 'about:blank');
|
||||
return curl_exec($ch);
|
||||
}
|
||||
|
||||
function go_back()
|
||||
{
|
||||
wp_safe_redirect(wp_validate_redirect(wp_unslash($_GET['next']), home_url()));
|
||||
}
|
||||
|
||||
function find_order($conn)
|
||||
{
|
||||
$id = intval($_POST['id']);
|
||||
$sql = 'SELECT * FROM orders WHERE id = ' . $id;
|
||||
return mysqli_query($conn, $sql);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
// Intentionally vulnerable PHP fixture for the quick-mode PHP rules. Do not deploy.
|
||||
// Each handler has one labeled weakness (benchmarks/manifests/php-vulnerable.toml); the fixed twin is
|
||||
// benchmarks/fixtures/php-benign/app.php.
|
||||
|
||||
$conn = mysqli_connect('localhost', 'app', 'app', 'app');
|
||||
|
||||
function order_by_code($code)
|
||||
{
|
||||
global $wpdb;
|
||||
return $wpdb->get_var("SELECT id FROM {$wpdb->prefix}orders WHERE code = '" . $code . "' LIMIT 1");
|
||||
}
|
||||
|
||||
function find_user($conn)
|
||||
{
|
||||
return mysqli_query($conn, "SELECT * FROM users WHERE name = '" . $_GET['name'] . "'");
|
||||
}
|
||||
|
||||
function members_sorted($order)
|
||||
{
|
||||
$order = esc_sql($order); // escaping does not protect an unquoted ORDER BY
|
||||
return " ORDER BY u.user_login {$order} ";
|
||||
}
|
||||
|
||||
function create_form($data)
|
||||
{
|
||||
return 'INSERT INTO forms (id, label) VALUES (' . $data['id'] . ', "x")';
|
||||
}
|
||||
|
||||
function captcha_cleanup($db, $userAgent)
|
||||
{
|
||||
$delete = sprintf(
|
||||
"DELETE FROM captcha WHERE useragent = '%s'",
|
||||
$userAgent
|
||||
);
|
||||
$db->query($delete);
|
||||
}
|
||||
|
||||
function ping()
|
||||
{
|
||||
system('ping -c 1 ' . $_GET['host']);
|
||||
}
|
||||
|
||||
function calculate($formula)
|
||||
{
|
||||
eval('$value = ' . $formula . ';');
|
||||
return $value;
|
||||
}
|
||||
|
||||
function restore_session()
|
||||
{
|
||||
return unserialize($_COOKIE['session']);
|
||||
}
|
||||
|
||||
function greet()
|
||||
{
|
||||
echo 'Hello ' . $_GET['name'];
|
||||
}
|
||||
|
||||
function page()
|
||||
{
|
||||
include $_GET['page'] . '.php';
|
||||
}
|
||||
|
||||
function download()
|
||||
{
|
||||
readfile('/var/www/files/' . $_GET['file']);
|
||||
}
|
||||
|
||||
function fetch_preview()
|
||||
{
|
||||
$ch = curl_init($_GET['url']);
|
||||
return curl_exec($ch);
|
||||
}
|
||||
|
||||
function go_back()
|
||||
{
|
||||
header('Location: ' . $_GET['next']);
|
||||
}
|
||||
|
||||
// Not visible to a line rule: the value reaches the sink through a variable on another line.
|
||||
function find_order($conn)
|
||||
{
|
||||
$id = $_POST['id'];
|
||||
$sql = "SELECT * FROM orders WHERE id = '" . $id . "'";
|
||||
return mysqli_query($conn, $sql);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
// Safe equivalents. None of these lines should produce a finding.
|
||||
|
||||
function order_by_code_safe($code)
|
||||
{
|
||||
global $wpdb;
|
||||
return $wpdb->get_var($wpdb->prepare("SELECT id FROM {$wpdb->prefix}orders WHERE code = %s LIMIT 1", $code));
|
||||
}
|
||||
|
||||
function orders_in_safe($ids)
|
||||
{
|
||||
global $wpdb;
|
||||
$ids_format = implode(', ', array_fill(0, count($ids), '%d'));
|
||||
return $wpdb->prepare('SELECT * FROM orders WHERE id IN (' . $ids_format . ')', $ids);
|
||||
}
|
||||
|
||||
function page_limit_safe($page)
|
||||
{
|
||||
return ' LIMIT ' . intval($page) . ', 20';
|
||||
}
|
||||
|
||||
function quoted_escaped_safe($name)
|
||||
{
|
||||
return "SELECT * FROM users WHERE name = '" . esc_sql($name) . "'";
|
||||
}
|
||||
|
||||
function ping_safe()
|
||||
{
|
||||
system('ping -c 1 ' . escapeshellarg($_GET['host']));
|
||||
}
|
||||
|
||||
function version_safe()
|
||||
{
|
||||
exec('git describe --tags', $output);
|
||||
return $output;
|
||||
}
|
||||
|
||||
function greet_safe()
|
||||
{
|
||||
echo 'Hello ' . htmlspecialchars($_GET['name'], ENT_QUOTES);
|
||||
}
|
||||
|
||||
function download_safe()
|
||||
{
|
||||
readfile('/var/www/files/' . basename($_GET['file']));
|
||||
}
|
||||
|
||||
function restore_safe($raw)
|
||||
{
|
||||
return unserialize($raw, ['allowed_classes' => false]);
|
||||
}
|
||||
|
||||
function query_args_safe()
|
||||
{
|
||||
$args = [];
|
||||
$args['include'] = [(int) $_REQUEST['p']];
|
||||
return $args;
|
||||
}
|
||||
|
||||
function comment_only()
|
||||
{
|
||||
// system('rm -rf ' . $_GET['dir']) would be dangerous but this is a comment
|
||||
# eval($_POST['code']) is a comment too
|
||||
return 'ok';
|
||||
}
|
||||
Reference in New Issue
Block a user