Skip to content

Instantly share code, notes, and snippets.

@joelpittet
Created September 13, 2015 05:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joelpittet/7bf0190a678a691fe188 to your computer and use it in GitHub Desktop.
Save joelpittet/7bf0190a678a691fe188 to your computer and use it in GitHub Desktop.
#!/usr/bin/env php
<?php
// ag --nonumbers --nofilename -C0 '[^\w](format_string|t)\(.+=\\?"[!@%][^"\W]+".*, array\([^\$]+\$' > ../modules.txt
// composer require pear/console_table
include 'vendor/autoload.php';
// Read in all the lines.
fopen("php://stdin", "r");
$text = '';
while (false !== ($line = fgets(STDIN))) {
$text .= $line . "\n";
}
preg_match_all('#[ts]\([\'"](.*)[\'"],\s*array\((.*)\)\s*\)#em', $text, $matches);
$suspects = [];
if (empty($matches[1]) || empty($matches[2])) {
print 'Something went wrong we have nothing.';
}
elseif (count($matches[1]) != count($matches[2])) {
print 'Not enough arguments to match strings';
}
else {
foreach ($matches[1] as $key => $text) {
$args = get_arguments($matches[2][$key]);
$non_protocol_risky_attributes = [
'id',
'class',
'language',
];
// Check if any of the arguments are in attributes.
foreach (array_keys($args) as $placeholder) {
$matched = preg_match('#(\w+)="' . preg_quote($placeholder) . '"#', $text, $attribute_match);
if ($matched && !empty($attribute_match[1])) {
$arg = $args[$placeholder];
$attribute_name = $attribute_match[1];
// Setup a base assumptions:
if ($placeholder[0] == '@') {
$result = 'potentially unsafe';
}
else {
$result = 'likely unsafe';
}
// Escaped title attributes are safe.
if ($placeholder[0] == '%') {
$result = 'WTF %';
}
elseif ($placeholder[0] == '@' && $attribute_name == 'title') {
$result = 'safe';
}
elseif (in_array($attribute_name, $non_protocol_risky_attributes)) {
// Really it should be @ check but likely not user variables.
// Not a protocol security risk.
if ($placeholder[0] == '!') {
$result = 'safeish';
}
else {
$result = 'safe';
}
}
elseif (strpos($arg, '$') === FALSE) {
// No variable for this arg. It's safe.
$result = 'safe';
}
else {
// Check which variable is being used.
preg_match_all('#\$[\w]+(\[[\w\"\']+\]|->[\w\{\}\$]+)?#', $arg, $arg_matches);
$is_safe = TRUE; // Innocent till proven guilty.
$safe_variables = [
// Theme globals.
"\$GLOBALS['theme']",
'$base_url',
'$base_path',
// Help URLS
'$help_url',
"\$data['help']",
"\$store_help_url",
'$config_path',
];
$safe_variable_patterns = '#^(\$node\->(type|nid)|\$\w+\->[a-z]id|\$\w+\->machine_name)$#';
foreach ($arg_matches[0] as $arg_match) {
if (in_array($arg_match, $safe_variables)) {
$is_safe = TRUE;
continue;
}
if (preg_match($safe_variable_patterns, $arg_match)) {
$is_safe = TRUE;
continue;
}
// Not trustworthy.
$is_safe = FALSE;
break;
}
// Double check any non-safe.
if (!$is_safe) {
if (strpos($arg, 'url(') === 0) {
$result = 'eh?';
}
if ((
strpos($arg, 'check_url(') === 0
|| strpos($arg, 'url(') === 0
)
&& $arg[strlen($arg) - 1] === ')') {
$result = 'safe';
}
elseif (strpos($arg, 'file_create_url(') === 0
|| strpos($arg, 'check_url(') !== FALSE) {
$result = 'likely safe';
}
}
else {
$result = 'safe';
}
}
$suspects[] = [
'placeholder' => $placeholder,
'text' => $text,
'attr' => $attribute_name,
'argument' => substr($arg, 0, 60),
'result' => $result,
];
}
}
}
}
// var_dump($suspects);
usort($suspects, function ($a, $b) {
return strcmp($a["result"], $b["result"]) + strcmp($a["placeholder"], $b["placeholder"]);
});
// Sort the results.
$sort = [];
foreach($suspects as $k => $v) {
$sort['result'][$k] = $v['result'];
$sort['placeholder'][$k] = $v['placeholder'];
}
# sort by result desc and then placeholder asc
array_multisort($sort['result'], SORT_ASC, $sort['placeholder'], SORT_ASC, $suspects);
$tbl = new Console_Table();
$tbl->setHeaders(['Attr', 'Placeholder', 'Argument', 'Context', 'Result']);
$safe_stats = [];
foreach ($suspects as $suspect) {
if (!isset($safe_stats[$suspect['result']])) {
$safe_stats[$suspect['result']] = 0;
}
$safe_stats[$suspect['result']]++;
$search_string = $suspect['attr'] . '="' . $suspect['placeholder'];
$index = strpos($suspect['text'], $search_string);
$context_before = 3;
$context_after = 5;
$start_index = ($index - $context_before) > 0 ? ($index - $context_before) : 0;
$context = substr($suspect['text'], $start_index, strlen($search_string) + $context_after);
$tbl->addRow([
$suspect['attr'],
$suspect['placeholder'],
$suspect['argument'],
$context,
$suspect['result'],
]);
}
print $tbl->getTable();
print "Suspects: " . count($suspects) . "\n";
print "----------------------------------\n";
foreach ($safe_stats as $name => $count) {
print ucwords($name) . ": " . $count . "\n";
}
function get_arguments($input) {
$return = [];
$items = str_getcsv($input);
foreach ($items as $item) {
if (strpos($item, '=>') === FALSE) {
print "missing assoc: $item\n";
var_dump($input);
continue;
}
list($key, $value) = explode('=>', $item);
$key = trim($key);
$value = trim($value);
$key = trim($key, '"\'');
$return[$key] = $value;
}
return $return;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment