programing

클라이언트 브라우저에서 Amazon S3 직접 파일 업로드 - 개인 키 노출

bestprogram 2023. 9. 4. 20:29

클라이언트 브라우저에서 Amazon S3 직접 파일 업로드 - 개인 키 노출

저는 서버 측 코드 없이 자바스크립트만 사용하여 클라이언트 머신에서 REST API를 통해 Amazon S3로 직접 파일 업로드를 구현하고 있습니다.다 잘 되는데 한 가지 걱정되는 것이 있습니다.

REST 때, 하고 Amazon S3 REST API에 서명을 .Authentication키를 .서명을 만들려면 개인 키를 사용해야 합니다.그러나 모든 것은 클라이언트 측에서 발생하므로 비밀키는 페이지 소스에서 쉽게 노출될 수 있습니다(소스를 난독화/암호화하더라도).

어떻게 하면 좋을까요?그리고 그것이 전혀 문제가 되지 않습니까?특정 개인 키 사용을 특정 CORS 오리진의 REST API 호출과 PUT 및 POST 메서드로만 제한하거나 키를 S3 및 특정 버킷에만 연결할 수 있습니까?다른 인증 방법이 있을 수 있습니까?

"서버리스" 솔루션이 이상적이지만, 서버에 파일을 업로드한 다음 S3로 전송하는 것을 제외하고 서버 측에서 처리하는 것을 고려할 수 있습니다.

당신이 원하는 것은 POST를 이용한 브라우저 기반 업로드라고 생각합니다.

기본적으로 서버 측 코드가 필요하지만 서명된 정책만 생성합니다.클라이언트 측 코드에 서명된 정책이 있으면 데이터가 서버를 통과하지 않고 POST를 사용하여 S3에 직접 업로드할 수 있습니다.

다음은 공식 문서 링크입니다.

다이어그램: http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingHTTPPOST.html

코드 예제: http://docs.aws.amazon.com/AmazonS3/latest/dev/HTTPPOSTExamples.html

서명된 정책은 다음과 같은 형식으로 HTML로 표시됩니다.

<html>
  <head>
    ...
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    ...
  </head>
  <body>
  ...
  <form action="http://johnsmith.s3.amazonaws.com/" method="post" enctype="multipart/form-data">
    Key to upload: <input type="input" name="key" value="user/eric/" /><br />
    <input type="hidden" name="acl" value="public-read" />
    <input type="hidden" name="success_action_redirect" value="http://johnsmith.s3.amazonaws.com/successful_upload.html" />
    Content-Type: <input type="input" name="Content-Type" value="image/jpeg" /><br />
    <input type="hidden" name="x-amz-meta-uuid" value="14365123651274" />
    Tags for File: <input type="input" name="x-amz-meta-tag" value="" /><br />
    <input type="hidden" name="AWSAccessKeyId" value="AKIAIOSFODNN7EXAMPLE" />
    <input type="hidden" name="Policy" value="POLICY" />
    <input type="hidden" name="Signature" value="SIGNATURE" />
    File: <input type="file" name="file" /> <br />
    <!-- The elements after this will be ignored -->
    <input type="submit" name="submit" value="Upload to Amazon S3" />
  </form>
  ...
</html>

FORM 작업은 서버를 통해가 아니라 S3로 파일을 직접 전송합니다.

사용자 중 한 명이 파일을 업로드하려고 할 때마다, 당신은 다음과 같이 만듭니다.POLICY그리고.SIGNATURE서버에 있습니다.페이지를 사용자의 브라우저로 되돌립니다.그러면 사용자는 서버를 거치지 않고 S3에 직접 파일을 업로드할 수 있습니다.

정책에 서명할 때 일반적으로 정책이 몇 분 후에 만료되도록 합니다.이렇게 하면 사용자가 업로드하기 전에 서버와 대화해야 합니다.이렇게 하면 원하는 경우 업로드를 모니터링하고 제한할 수 있습니다.

서버에서 송수신되는 유일한 데이터는 서명된 URL입니다.비밀 키는 서버에서 비밀로 유지됩니다.

AWS S3 Cognition에서 이를 수행하여 다음 링크를 시도할 수 있습니다.

http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/browser-examples.html#Amazon_S3

또한 이 코드를 사용해 보십시오.

지역, IdentityPoolId 및 버킷 이름만 변경하면 됩니다.

<!DOCTYPE html>
<html>

<head>
    <title>AWS S3 File Upload</title>
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.1.12.min.js"></script>
</head>

<body>
    <input type="file" id="file-chooser" />
    <button id="upload-button">Upload to S3</button>
    <div id="results"></div>
    <script type="text/javascript">
    AWS.config.region = 'your-region'; // 1. Enter your region

    AWS.config.credentials = new AWS.CognitoIdentityCredentials({
        IdentityPoolId: 'your-IdentityPoolId' // 2. Enter your identity pool
    });

    AWS.config.credentials.get(function(err) {
        if (err) alert(err);
        console.log(AWS.config.credentials);
    });

    var bucketName = 'your-bucket'; // Enter your bucket name
    var bucket = new AWS.S3({
        params: {
            Bucket: bucketName
        }
    });

    var fileChooser = document.getElementById('file-chooser');
    var button = document.getElementById('upload-button');
    var results = document.getElementById('results');
    button.addEventListener('click', function() {

        var file = fileChooser.files[0];

        if (file) {

            results.innerHTML = '';
            var objKey = 'testing/' + file.name;
            var params = {
                Key: objKey,
                ContentType: file.type,
                Body: file,
                ACL: 'public-read'
            };

            bucket.putObject(params, function(err, data) {
                if (err) {
                    results.innerHTML = 'ERROR: ' + err;
                } else {
                    listObjs();
                }
            });
        } else {
            results.innerHTML = 'Nothing to upload.';
        }
    }, false);
    function listObjs() {
        var prefix = 'testing';
        bucket.listObjects({
            Prefix: prefix
        }, function(err, data) {
            if (err) {
                results.innerHTML = 'ERROR: ' + err;
            } else {
                var objKeys = "";
                data.Contents.forEach(function(obj) {
                    objKeys += obj.Key + "<br>";
                });
                results.innerHTML = objKeys;
            }
        });
    }
    </script>
</body>

</html>

For more details, Please check - 깃헙

"서버리스" 솔루션을 원한다는 말씀이시군요.그러나 이는 "당신의" 코드를 루프에 넣을 수 없다는 것을 의미합니다. (참고: 고객에게 코드를 제공하면 이제 "그들의" 코드입니다.)CORS를 잠그는 것은 도움이 되지 않습니다. 사람들은 시스템을 악용하기 위해 올바른 CORS 헤더를 추가하는 웹 기반 도구(또는 웹 기반 프록시)를 쉽게 작성할 수 있습니다.

가장 큰 문제는 사용자를 구분할 수 없다는 것입니다.한 사용자가 자신의 파일을 나열/액세스하도록 허용할 수는 없지만 다른 사용자는 이를 금지할 수 있습니다.남용을 발견하면 키를 변경하는 것 외에는 할 수 있는 일이 없습니다. (공격자는 이 키를 다시 얻을 수 있을 것입니다.)

가장 좋은 방법은 Javascript 클라이언트의 키로 "IAM 사용자"를 만드는 것입니다.버킷 하나에 대해서만 쓰기 권한을 부여합니다. (그러나 이상적으로 ListBucket 작업을 활성화하지 않는 것이 좋습니다. 그러면 공격자에게 더 매력적일 것입니다.)

서버(단순한 마이크로 인스턴스라도 월 20달러)가 있다면 실시간으로 남용을 모니터링하고 방지하면서 서버에서 키에 서명할 수 있습니다.서버가 없는 경우에는 사후 악용 여부를 주기적으로 모니터링하는 것이 최선입니다.제가 할 일은 다음과 같습니다.

IAM 사용자의 키를 주기적으로 회전합니다.매일 밤 해당 IAM 사용자에 대한 새 키를 생성하고 가장 오래된 키를 교체합니다.키가 2개이므로 각 키는 2일간 유효합니다.

S3 로깅을 활성화하고 매 시간 로그를 다운로드합니다."너무 많은 업로드" 및 "너무 많은 다운로드"에 대한 경고를 설정합니다.전체 파일 크기와 업로드된 파일 수를 모두 확인하려고 합니다.또한 글로벌 총계와 IP 주소별 총계(낮은 임계값)를 모두 모니터링하려고 합니다.

이러한 검사는 데스크톱에서 실행할 수 있기 때문에 "서버리스"로 수행할 수 있습니다.(즉, S3가 모든 작업을 수행합니다. 이러한 프로세스는 S3 버킷의 남용을 경고하기 위한 것이므로 월말에 거액의 AWS 청구서를 받지 못합니다.)

승인된 답변에 더 많은 정보를 추가하면 제 블로그를 참조하여 AWS Signature 버전 4를 사용하여 실행 중인 코드 버전을 확인할 수 있습니다.

다음과 같이 요약합니다.

사용자가 업로드할 파일을 선택하는 즉시 다음 작업을 수행합니다. 1. 웹 서버에 전화를 걸어 필요한 매개 변수를 생성하는 서비스를 시작합니다.

  1. 이 서비스에서 AWS IAM 서비스에 전화하여 임시 신용장을 받으십시오.

  2. 자격 증명이 생성되면 버킷 정책(기본 64 인코딩 문자열)을 생성합니다.그런 다음 임시 비밀 액세스 키로 버킷 정책에 서명하여 최종 서명을 생성합니다.

  3. 필요한 매개 변수를 UI로 다시 보냅니다.

  4. 이 메시지가 수신되면 html 양식 개체를 만들고 필요한 매개 변수를 설정한 후 POST합니다.

자세한 내용은 https://wordpress1763.wordpress.com/2016/10/03/browser-based-upload-aws-signature-version-4/ 를 참조하십시오.

서명을 만들려면 개인 키를 사용해야 합니다.그러나 모든 것은 클라이언트 측에서 발생하므로 비밀키는 페이지 소스에서 쉽게 노출될 수 있습니다(소스를 난독화/암호화하더라도).

이것이 당신이 오해한 부분입니다.디지털 서명을 사용하는 바로 그 이유는 비밀 키를 드러내지 않고도 올바른 것을 확인할 수 있기 때문입니다.이 경우 사용자가 양식 게시물에 대해 설정한 정책을 수정하지 못하도록 디지털 서명이 사용됩니다.

여기와 같은 디지털 서명은 모든 웹에서 보안을 위해 사용됩니다.만약 누군가(NSA?)가 정말로 그것들을 깰 수 있다면, 그들은 당신의 S3 버킷보다 훨씬 더 큰 목표를 갖게 될 것입니다 :)

Javascript 브라우저에서 AWS S3로 파일을 업로드하고 S3 버킷에 있는 모든 파일을 나열할 수 있는 간단한 코드를 알려드렸습니다.

단계:

  1. Create IdentityPoolId http://docs.aws.amazon.com/cognito/latest/developerguide/identity-pools.html 을 생성하는 방법에 대해 알아봅니다.

    1. S3의 콘솔 페이지로 이동하여 버킷 속성에서 Cors 구성을 열고 다음 XML 코드를 입력합니다.

      <?xml version="1.0" encoding="UTF-8"?>
      <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
       <CORSRule>    
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <AllowedMethod>DELETE</AllowedMethod>
        <AllowedMethod>HEAD</AllowedMethod>
        <AllowedHeader>*</AllowedHeader>
       </CORSRule>
      </CORSConfiguration>
      
    2. 다음 코드가 포함된 HTML 파일을 만들고 자격 증명을 변경한 후 브라우저에서 파일을 열고 즐기십시오.

      <script type="text/javascript">
       AWS.config.region = 'ap-north-1'; // Region
       AWS.config.credentials = new AWS.CognitoIdentityCredentials({
       IdentityPoolId: 'ap-north-1:*****-*****',
       });
       var bucket = new AWS.S3({
       params: {
       Bucket: 'MyBucket'
       }
       });
      
       var fileChooser = document.getElementById('file-chooser');
       var button = document.getElementById('upload-button');
       var results = document.getElementById('results');
      
       function upload() {
       var file = fileChooser.files[0];
       console.log(file.name);
      
       if (file) {
       results.innerHTML = '';
       var params = {
       Key: n + '.pdf',
       ContentType: file.type,
       Body: file
       };
       bucket.upload(params, function(err, data) {
       results.innerHTML = err ? 'ERROR!' : 'UPLOADED.';
       });
       } else {
       results.innerHTML = 'Nothing to upload.';
       }    }
      </script>
      <body>
       <input type="file" id="file-chooser" />
       <input type="button" onclick="upload()" value="Upload to S3">
       <div id="results"></div>
      </body>
      

서버 사이드 코드가 없는 경우 클라이언트 측의 JavaScript 코드에 대한 액세스 보안에 따라 보안이 달라집니다(코드를 가진 모든 사용자가 무언가를 업로드할 수 있음).

따라서 클라이언트 측에서 서명된 구성 요소가 필요하지 않도록 공용으로 쓰기 가능하지만 읽기 불가능한 특수 S3 버킷을 만드는 것이 좋습니다.

버킷 이름(GUIDeg)은 악의적인 업로드에 대한 유일한 방어 수단이 될 것입니다(그러나 잠재적인 공격자는 버킷이 자신에게만 쓰여지기 때문에 버킷을 사용하여 데이터를 전송할 수 없습니다).

노드 및 서버리스를 사용하여 정책 문서를 생성하는 방법은 다음과 같습니다.

"use strict";

const uniqid = require('uniqid');
const crypto = require('crypto');

class Token {

    /**
     * @param {Object} config SSM Parameter store JSON config
     */
    constructor(config) {

        // Ensure some required properties are set in the SSM configuration object
        this.constructor._validateConfig(config);

        this.region = config.region; // AWS region e.g. us-west-2
        this.bucket = config.bucket; // Bucket name only
        this.bucketAcl = config.bucketAcl; // Bucket access policy [private, public-read]
        this.accessKey = config.accessKey; // Access key
        this.secretKey = config.secretKey; // Access key secret

        // Create a really unique videoKey, with folder prefix
        this.key = uniqid() + uniqid.process();

        // The policy requires the date to be this format e.g. 20181109
        const date = new Date().toISOString();
        this.dateString = date.substr(0, 4) + date.substr(5, 2) + date.substr(8, 2);

        // The number of minutes the policy will need to be used by before it expires
        this.policyExpireMinutes = 15;

        // HMAC encryption algorithm used to encrypt everything in the request
        this.encryptionAlgorithm = 'sha256';

        // Client uses encryption algorithm key while making request to S3
        this.clientEncryptionAlgorithm = 'AWS4-HMAC-SHA256';
    }

    /**
     * Returns the parameters that FE will use to directly upload to s3
     *
     * @returns {Object}
     */
    getS3FormParameters() {
        const credentialPath = this._amazonCredentialPath();
        const policy = this._s3UploadPolicy(credentialPath);
        const policyBase64 = new Buffer(JSON.stringify(policy)).toString('base64');
        const signature = this._s3UploadSignature(policyBase64);

        return {
            'key': this.key,
            'acl': this.bucketAcl,
            'success_action_status': '201',
            'policy': policyBase64,
            'endpoint': "https://" + this.bucket + ".s3-accelerate.amazonaws.com",
            'x-amz-algorithm': this.clientEncryptionAlgorithm,
            'x-amz-credential': credentialPath,
            'x-amz-date': this.dateString + 'T000000Z',
            'x-amz-signature': signature
        }
    }

    /**
     * Ensure all required properties are set in SSM Parameter Store Config
     *
     * @param {Object} config
     * @private
     */
    static _validateConfig(config) {
        if (!config.hasOwnProperty('bucket')) {
            throw "'bucket' is required in SSM Parameter Store Config";
        }
        if (!config.hasOwnProperty('region')) {
            throw "'region' is required in SSM Parameter Store Config";
        }
        if (!config.hasOwnProperty('accessKey')) {
            throw "'accessKey' is required in SSM Parameter Store Config";
        }
        if (!config.hasOwnProperty('secretKey')) {
            throw "'secretKey' is required in SSM Parameter Store Config";
        }
    }

    /**
     * Create a special string called a credentials path used in constructing an upload policy
     *
     * @returns {String}
     * @private
     */
    _amazonCredentialPath() {
        return this.accessKey + '/' + this.dateString + '/' + this.region + '/s3/aws4_request';
    }

    /**
     * Create an upload policy
     *
     * @param {String} credentialPath
     *
     * @returns {{expiration: string, conditions: *[]}}
     * @private
     */
    _s3UploadPolicy(credentialPath) {
        return {
            expiration: this._getPolicyExpirationISODate(),
            conditions: [
                {bucket: this.bucket},
                {key: this.key},
                {acl: this.bucketAcl},
                {success_action_status: "201"},
                {'x-amz-algorithm': 'AWS4-HMAC-SHA256'},
                {'x-amz-credential': credentialPath},
                {'x-amz-date': this.dateString + 'T000000Z'}
            ],
        }
    }

    /**
     * ISO formatted date string of when the policy will expire
     *
     * @returns {String}
     * @private
     */
    _getPolicyExpirationISODate() {
        return new Date((new Date).getTime() + (this.policyExpireMinutes * 60 * 1000)).toISOString();
    }

    /**
     * HMAC encode a string by a given key
     *
     * @param {String} key
     * @param {String} string
     *
     * @returns {String}
     * @private
     */
    _encryptHmac(key, string) {
        const hmac = crypto.createHmac(
            this.encryptionAlgorithm, key
        );
        hmac.end(string);

        return hmac.read();
    }

    /**
     * Create an upload signature from provided params
     * https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html#signing-request-intro
     *
     * @param policyBase64
     *
     * @returns {String}
     * @private
     */
    _s3UploadSignature(policyBase64) {
        const dateKey = this._encryptHmac('AWS4' + this.secretKey, this.dateString);
        const dateRegionKey = this._encryptHmac(dateKey, this.region);
        const dateRegionServiceKey = this._encryptHmac(dateRegionKey, 's3');
        const signingKey = this._encryptHmac(dateRegionServiceKey, 'aws4_request');

        return this._encryptHmac(signingKey, policyBase64).toString('hex');
    }
}

module.exports = Token;

사용된 구성 개체는 SSM 매개 변수 저장소에 저장되며 다음과 같습니다.

{
    "bucket": "my-bucket-name",
    "region": "us-west-2",
    "bucketAcl": "private",
    "accessKey": "MY_ACCESS_KEY",
    "secretKey": "MY_SECRET_ACCESS_KEY",
}

타사 서비스를 사용할 의향이 있는 경우 auth0.com 에서 이러한 통합을 지원합니다.auth0 서비스가 타사 SSO 서비스 인증을 AWS 임시 세션 토큰으로 교환하면 사용 권한이 제한됩니다.

참조: https://github.com/auth0-samples/auth0-s3-sample/
및 auth0 설명서를 참조하십시오.

VueJS 및 Go를 기반으로 UI를 생성하여 이진 파일을 AWS Secrets Manager https://github.com/ledongthuc/awssecretsmanagerui 에 업로드했습니다.

보안 파일을 업로드하고 텍스트 데이터를 쉽게 업데이트할 수 있습니다.당신이 원한다면 참조할 수 있습니다.

언급URL : https://stackoverflow.com/questions/17585881/amazon-s3-direct-file-upload-from-client-browser-private-key-disclosure