Skip to content

Commit

Permalink
feat: fix SSRF when download avatar
Browse files Browse the repository at this point in the history
  • Loading branch information
leo220yuyaodog committed Oct 11, 2022
1 parent 5243aab commit 107d85e
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 26 deletions.
12 changes: 9 additions & 3 deletions object/avatar.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func downloadFile(url string) (*bytes.Buffer, error) {
return fileBuffer, nil
}

func getPermanentAvatarUrl(organization string, username string, url string) string {
func getPermanentAvatarUrl(organization string, username string, url string, upload bool) string {
if url == "" {
return ""
}
Expand All @@ -62,6 +62,14 @@ func getPermanentAvatarUrl(organization string, username string, url string) str
fullFilePath := fmt.Sprintf("/avatar/%s/%s.png", organization, username)
uploadedFileUrl, _ := getUploadFileUrl(defaultStorageProvider, fullFilePath, false)

if upload {
DownloadAndUpload(url, fullFilePath)
}

return uploadedFileUrl
}

func DownloadAndUpload(url string, fullFilePath string) {
fileBuffer, err := downloadFile(url)
if err != nil {
panic(err)
Expand All @@ -71,6 +79,4 @@ func getPermanentAvatarUrl(organization string, username string, url string) str
if err != nil {
panic(err)
}

return uploadedFileUrl
}
2 changes: 1 addition & 1 deletion object/avatar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestSyncPermanentAvatars(t *testing.T) {
continue
}

user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, true)
updateUserColumn("permanent_avatar", user)
fmt.Printf("[%d/%d]: Update user: [%s]'s permanent avatar: %s\n", i, len(users), user.GetId(), user.PermanentAvatar)
}
Expand Down
2 changes: 1 addition & 1 deletion object/syncer_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (syncer *Syncer) updateUserForOriginalFields(user *User) (bool, error) {
}

if user.Avatar != oldUser.Avatar && user.Avatar != "" {
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, true)
}

columns := syncer.getCasdoorColumns()
Expand Down
8 changes: 4 additions & 4 deletions object/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
user.UpdateUserHash()

if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" {
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
}

if len(columns) == 0 {
Expand Down Expand Up @@ -419,7 +419,7 @@ func UpdateUserForAllFields(id string, user *User) bool {
user.UpdateUserHash()

if user.Avatar != oldUser.Avatar && user.Avatar != "" {
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
}

affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(user)
Expand Down Expand Up @@ -449,7 +449,7 @@ func AddUser(user *User) bool {
user.UpdateUserHash()
user.PreHash = user.Hash

user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)

user.Ranking = GetUserCount(user.Owner, "", "") + 1

Expand All @@ -474,7 +474,7 @@ func AddUsers(users []*User) bool {
user.UpdateUserHash()
user.PreHash = user.Hash

user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, true)
}

affected, err := adapter.Engine.Insert(users)
Expand Down
66 changes: 60 additions & 6 deletions web/src/CropperDiv.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import React, {useState} from "react";
import React, {useEffect, useState} from "react";
import Cropper from "react-cropper";
import "cropperjs/dist/cropper.css";
import * as Setting from "./Setting";
import {Button, Col, Modal, Row} from "antd";
import {Button, Col, Modal, Row, Select} from "antd";
import i18next from "i18next";
import * as ResourceBackend from "./backend/ResourceBackend";

export const CropperDiv = (props) => {
const [loading, setLoading] = useState(true);
const [options, setOptions] = useState([]);
const [image, setImage] = useState("");
const [cropper, setCropper] = useState();
const [visible, setVisible] = React.useState(false);
const [confirmLoading, setConfirmLoading] = React.useState(false);
const [visible, setVisible] = useState(false);
const [confirmLoading, setConfirmLoading] = useState(false);
const {title} = props;
const {user} = props;
const {buttonText} = props;
Expand Down Expand Up @@ -60,7 +62,7 @@ export const CropperDiv = (props) => {
ResourceBackend.uploadResource(user.owner, user.name, "avatar", "CropperDiv", fullFilePath, blob)
.then((res) => {
if (res.status === "ok") {
window.location.href = "/account";
window.location.href = window.location.pathname;
} else {
Setting.showMessage("error", res.msg);
}
Expand Down Expand Up @@ -88,6 +90,48 @@ export const CropperDiv = (props) => {
uploadButton.click();
};

const getOptions = (data) => {
const options = [];
if (props.account.organization.defaultAvatar !== null) {
options.push({value: props.account.organization.defaultAvatar});
}
for (let i = 0; i < data.length; i++) {
if (data[i].fileType === "image") {
const url = `${data[i].url}`;
options.push({
value: url,
});
}
}
return options;
};

const getBase64Image = (src) => {
return new Promise((resolve) => {
const image = new Image();
image.src = src;
image.setAttribute("crossOrigin", "anonymous");
image.onload = () => {
const canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, image.width, image.height);
const dataURL = canvas.toDataURL("image/png");
resolve(dataURL);
};
});
};

useEffect(() => {
setLoading(true);
ResourceBackend.getResources(props.account.owner, props.account.name, "", "", "", "", "", "")
.then((res) => {
setLoading(false);
setOptions(getOptions(res));
});
}, []);

return (
<div>
<Button type="default" onClick={showModal}>
Expand All @@ -105,10 +149,20 @@ export const CropperDiv = (props) => {
[<Button block key="submit" type="primary" onClick={handleOk}>{i18next.t("user:Set new profile picture")}</Button>]
}
>
<Col style={{margin: "0px auto 40px auto", width: 1000, height: 300}}>
<Col style={{margin: "0px auto 60px auto", width: 1000, height: 350}}>
<Row style={{width: "100%", marginBottom: "20px"}}>
<input style={{display: "none"}} ref={input => uploadButton = input} type="file" accept="image/*" onChange={onChange} />
<Button block onClick={selectFile}>{i18next.t("user:Select a photo...")}</Button>
<Select
style={{width: "100%"}}
loading={loading}
placeholder={i18next.t("user:Please select avatar from resources")}
onChange={(async value => {
setImage(await getBase64Image(value));
})}
options={options}
allowClear={true}
/>
</Row>
<Cropper
style={{height: "100%"}}
Expand Down
11 changes: 0 additions & 11 deletions web/src/UserEditPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {Button, Card, Col, Input, Result, Row, Select, Spin, Switch} from "antd"
import * as UserBackend from "./backend/UserBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting";
import {LinkOutlined} from "@ant-design/icons";
import i18next from "i18next";
import CropperDiv from "./CropperDiv.js";
import * as ApplicationBackend from "./backend/ApplicationBackend";
Expand Down Expand Up @@ -232,16 +231,6 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Avatar"), i18next.t("general:Avatar - Tooltip"))} :
</Col>
<Col span={22} >
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:URL")}:
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined />} value={this.state.user.avatar} onChange={e => {
this.updateUserField("avatar", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Preview")}:
Expand Down

0 comments on commit 107d85e

Please sign in to comment.