今天,由于找东西,突然链接跳到了简书,心想这么久都没登录过简书了,就来登录一下,看看有什么消息没,这一登不知道,一登吓一跳,我的简书竟然被人攻击了,产生了很多莫名的服务信息(由于简书审核,所以只能马赛克了)
看了下这个文集,应该有一百来条的文章基本都是垃圾文章,本来想删文集来着,就在我下定决定按下的时候,突然萌生了一个想法,让程序来删除
看着这么多条服务信息,心里还是有一丝安慰的,竟然这么瞧得起我,于是心生一计,干脆写个程序来删除吧,一想到还能服务大众,越想就越觉得有意思,说干就干
- 由于是已经删除完了垃圾文章后,突然想写篇文章,所以上面的图都是程序删除完后,浏览器上仅有的原始信息了,所以下面的文章列表都是自己虚构的
- 由于我的整个文集都是垃圾文章,所以如果有人要尝试的话,一定要记得备份,备份,备份
- 可能会有人问,直接删除文集不就行了吗,哈哈,作为程序员,喜欢倒腾,就这么点爱好了
- 可以研究下调用删除文集的接口,应该和删除文章的差不多
获取被攻击的文集
当然咯,第一步要做的肯定就是需要将这些垃圾文章的列表拿到对吧,这样后面的操作才有意义,根据我这么多年敏锐的观察力,一眼就瞅到了对应的连接(说明简书的开发者还是比较有规范的,所以才这么好找,简书看到了记得给广告费)
获取删除文章的连接
这个就更简单啦,随便在这个文集下面选择一个文章,然后进行删除,就能看出大概的规则了,结合上面获取的列表可以看出,简书删除文章的时候,其实就是将文章的id传输,拼装成一个url来进行删除的
好了,以上获取和删除的逻辑我们现在都已经了解了,接下来就是用程序来实现了,想了想,很久都没有用go语言了,所以就决定临幸一下go语言(其实是因为简单)
程序获取攻击的文章
首先,我们需要对这个获取文章的url进行分析,看传递了什么参数,当然,经验老到的人肯定一眼都瞟出其中的端倪
en,用的是cookie,这就没啥了,接下来程序实现
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
http "net/http"
)
type Course []struct {
ID int `json:"id"`
}
func main() {
client:=&http.Client{}
cookie:="read_mode=day; default_font=font2; locale=zh-CN; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%22176d642da4470a-086ef9f560f1e38-44586b-2073600-176d642da4513f%22%2C%22first_id%22%3A%22%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%2C%22%24latest_referrer_host%22%3A%22%22%7D%2C%22%24device_id%22%3A%22…user=1; __yadk_uid=Y5sqV7tE8oQ3X7h8V2hNiJs9Q27Nvkk; Hm_lvt_0c0e9d9b1e7d617b3e6842e85b9fb068=1609912474; Hm_lpvt_0c0e9d9b1e7d617b3e6842e85b9fb068=1609914436; _m7e_session_core=96e43f62507e2f5f9ac1fb43ddbcb574; remember_user_token=W1s2Nzk5NjQ1XSwiJDJhJDExJGtSdnUzeldlbnhHeDYvb0RYb2xtSWUiLCIxNjA5OTEyNTEyLjkxMzA1ODgiXQ%3D%3D--a6f6577b77a8a6b9305392e4b5fa8e67716c2886; web_login_version=MTYwOTkxMjUxMg%3D%3D--30269124b31f829f8dc78c3e88183b3e9bc94172; _ga=GA1.2.306275077.1609912570; _gid=GA1.2.806692222.1609912570"
url:="https://www.jianshu.com/author/notebooks/14164901/notes"
req,err:=http.NewRequest("GET",url,nil)
req.Header.Add("Cookie",cookie)
//ua:="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:84.0) Gecko/20100101 Firefox/84.0"
//req.Header.Add("User-Agent",ua)
//req.Header.Add("If-None-Match","W/\"64d5260ebd8004b8d4007e68c8c9d0cc")
//req.Header.Add("Referer","https://www.jianshu.com/writer")
req.Header.Add("Accept","application/json")
res,err:=client.Do(req)
if err!=nil{
fmt.Println("get url %s fail",url)
return
}
var course Course
c,err:=ioutil.ReadAll(res.Body)
if err := json.Unmarshal(c, &course); err != nil {
panic(err)
}
fmt.Println(course)
}
大概解读下:
- 因为请求需要携带cookie,所以直接使用http.Get就不行啦,需要自己来进行构建了,这里的cookie,需要获取浏览器中自己的cookie哦
- 这里需要注意的点,就是Header里面需要加Accept这个头,否则返回的状态码是406
- 为了方便展示呢,就把io流进行了转换,然后进行了输出,看看结果是不是对的
- 由于整个流程我们只需要id,所以就没有对其他字段进行转化,有兴趣的可以在Course里面自己添加即可
因为我总共就创建了两篇测试文章,刚刚为了给你们看删除的逻辑进行截图,所以,现在程序获取就只能获取到一个了,别问我为什么不多创建个,因为懒,哈哈,还有就是不想前面重新截图,看下程序的输出结果
和浏览器的结果比对一下
嗯,和程序的结果一致,吾心甚慰,接下来就是终极大招,删除步骤的书写了
其实感觉简书做的还是勉强吧,比如我以为它会做csrf攻击之类的,结果没有,以为他要做ua之类的拦截的,还是没有,嗯,先这样吧,希望简书的开发者看到了不要喷我
程序删除攻击文章
既然文章的id都获取到了,那么删除文章,就只需要对连接进行拼装,然后发起一个post请求就可以了,所以基本上和get获取文章列表差不多,唯一需要注意的点呢,就是因为文章id是个数组,我们只需要写个循环进行调用接口,对吧,就这么简单
for _,val :=range course{
fmt.Println("begin delete Title:", val.ID)
titleUrl :="https://www.jianshu.com/author/notes/"+strconv.Itoa(val.ID)+"/soft_destroy"
req,_:=http.NewRequest("POST",titleUrl,nil)
req.Header.Add("Cookie",cookie)
//ua:="Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:84.0) Gecko/20100101 Firefox/84.0"
//req.Header.Add("User-Agent",ua)
//req.Header.Add("If-None-Match","W/\"64d5260ebd8004b8d4007e68c8c9d0cc")
//req.Header.Add("Referer","https://www.jianshu.com/writer")
req.Header.Add("Accept","application/json")
res,_:=client.Do(req)
defer res.Body.Close()
buf := new(bytes.Buffer)
buf.ReadFrom(res.Body)
newStr := buf.String()
fmt.Println("delete res:",newStr)
}
这里只是列举了删除片段,整个逻辑最后贴出
注意的点:
- 因为我们的标题id是个int类型,所以拼装的时候需要将其转换成string,然后进行拼接,
- 这里将删除请求的响应内容打印,看和浏览器的响应结果是否一致
- 较真的话,在删除的时候,可以考虑使用并发来进行删除文章,因为互不影响,可以提高效率
这么一看,跟我们在浏览器上面的响应结果一致,所以再去我们的文集看看还有没有垃圾文章
可以看到,整个世界都清净了
完整代码
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
http "net/http"
"strconv"
)
type Course []struct {
ID int `json:"id"`
}
func main() {
client:=&http.Client{}
cookie:="read_mode=day; default_font=font2; locale=zh-CN; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%22176d642da4470a-086ef9f560f1e38-44586b-2073600-176d642da4513f%22%2C%22first_id%22%3A%22%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%2C%22%24latest_referrer_host%22%3A%22%22%7D%2C%22%24device_id%22%3A%22…user=1; __yadk_uid=Y5sqV7tE8oQ3X7h8V2hNiJs9Q27Nvkk; Hm_lvt_0c0e9d9b1e7d617b3e6842e85b9fb068=1609912474; Hm_lpvt_0c0e9d9b1e7d617b3e6842e85b9fb068=1609914436; _m7e_session_core=96e43f62507e2f5f9ac1fb43ddbcb574; remember_user_token=W1s2Nzk5NjQ1XSwiJDJhJDExJGtSdnUzeldlbnhHeDYvb0RYb2xtSWUiLCIxNjA5OTEyNTEyLjkxMzA1ODgiXQ%3D%3D--a6f6577b77a8a6b9305392e4b5fa8e67716c2886; web_login_version=MTYwOTkxMjUxMg%3D%3D--30269124b31f829f8dc78c3e88183b3e9bc94172; _ga=GA1.2.306275077.1609912570; _gid=GA1.2.806692222.1609912570"
url:="https://www.jianshu.com/author/notebooks/14164901/notes"
req,err:=http.NewRequest("GET",url,nil)
req.Header.Add("Cookie",cookie)
req.Header.Add("Accept","application/json")
res,err:=client.Do(req)
if err!=nil{
fmt.Println("get url %s fail",url)
return
}
var course Course
c,err:=ioutil.ReadAll(res.Body)
if err := json.Unmarshal(c, &course); err != nil {
panic(err)
}
//fmt.Println(course)
for _,val :=range course{
fmt.Println("begin delete Title:", val.ID)
titleUrl :="https://www.jianshu.com/author/notes/"+strconv.Itoa(val.ID)+"/soft_destroy"
req,_:=http.NewRequest("POST",titleUrl,nil)
req.Header.Add("Cookie",cookie)
req.Header.Add("Accept","application/json")
res,_:=client.Do(req)
defer res.Body.Close()
buf := new(bytes.Buffer)
buf.ReadFrom(res.Body)
newStr := buf.String()
fmt.Println("delete res:",newStr)
}
}