[하루한줄] CVE-2024-25106: OpenObserve의 Improper Access Control 취약점
URL
https://github.com/openobserve/openobserve/security/advisories/GHSA-3m5f-9m66-xgp7
Target
- OpenObserve < 0.8.0
Explain
OpenObserve는 클라우드 기반 옵저버빌리티 플랫폼으로 데이터의 로깅, 분석, 추적에 특화되어 있습니다. OpenObserve는 보다 간단한 설정을 지원하고 사용자는 API를 통한 데이터의 수집, 검색 등이 가능합니다.
취약점은 이 API 중에서 유저를 삭제하는데 사용하는 /api/{org_id}/users/{email_id}
엔드포인트에 있습니다. /src/service/users.rs
에 정의된 remove_user_from_org
는 아래와 같이 DELETE 요청된 유저를 삭제하는 기능을 하는데 이 과정에서 request를 만든 사용자가 유저를 삭제할 권한을 가지고 있는지 확인하지 않습니다.
pub async fn remove_user_from_org(org_id: &str, email_id: &str) -> Result<HttpResponse, Error> {
let ret_user = db::user::get_db_user(email_id).await;
match ret_user {
Ok(mut user) => {
if !user.organizations.is_empty() {
let mut orgs = user.clone().organizations;
if orgs.len() == 1 {
let _ = db::user::delete(email_id).await;
} else {
orgs.retain(|x| !x.name.eq(&org_id.to_string()));
user.organizations = orgs;
let resp = db::user::set(user).await;
// special case as we cache flattened user struct
if resp.is_ok() {
USERS.remove(&format!("{org_id}/{email_id}"));
}
}
Ok(HttpResponse::Ok().json(MetaHttpResponse::message(
http::StatusCode::OK.into(),
"User removed from organization".to_string(),
)))
} else {
Ok(HttpResponse::NotFound().json(MetaHttpResponse::error(
StatusCode::NOT_FOUND.into(),
"User for the organization not found".to_string(),
)))
}
}
Err(_) => Ok(HttpResponse::NotFound().json(MetaHttpResponse::error(
StatusCode::NOT_FOUND.into(),
"User for the organization not found".to_string(),
))),
}
}
따라서 권한이 없는 유저도 같은 organization에 속한 다른 유저를 삭제할 수 있고 Admin이나 Root처럼 더 높은 권한 수준을 가지는 유저 또한 삭제할 수 있습니다.
이 취약점의 패치는 취약한 함수인 remove_user_from_org
에서 아래와 같이 요청한 사용자의 권한을 확인하는 부분을 추가하는 것으로 이루어졌습니다.
pub async fn remove_user_from_org(
org_id: &str,
email_id: &str,
initiator_id: &str,// 요청을 발생시킨 사용자 정보를 확인하기 위한 새로운 파라미터
) -> Result<HttpResponse, Error> {
let initiating_user = if is_root_user(initiator_id) {
ROOT_USER.get("root").unwrap().clone()
} else {
db::user::get(Some(org_id), initiator_id)
.await
.unwrap()
.unwrap()
};
if initiating_user.role.eq(&UserRole::Root) || initiating_user.role.eq(&UserRole::Admin) {// 요청자가 Root 또는 Admin인지 검사
let ret_user = db::user::get_db_user(email_id).await;
match ret_user {
Ok(mut user) => {
if !user.organizations.is_empty() {
let mut orgs = user.clone().organizations;
if orgs.len() == 1 {
let _ = db::user::delete(email_id).await;
} else {
orgs.retain(|x| !x.name.eq(&org_id.to_string()));
user.organizations = orgs;
let resp = db::user::set(user).await;
// special case as we cache flattened user struct
if resp.is_ok() {
USERS.remove(&format!("{org_id}/{email_id}"));
}
}
Ok(HttpResponse::Ok().json(MetaHttpResponse::message(
http::StatusCode::OK.into(),
"User removed from organization".to_string(),
)))
} else {
Ok(HttpResponse::NotFound().json(MetaHttpResponse::error(
StatusCode::NOT_FOUND.into(),
"User for the organization not found".to_string(),
)))
}
}
Err(_) => Ok(HttpResponse::NotFound().json(MetaHttpResponse::error(
StatusCode::NOT_FOUND.into(),
"User for the organization not found".to_string(),
))),
}
} else {
Ok(HttpResponse::Unauthorized().json(MetaHttpResponse::error(
StatusCode::UNAUTHORIZED.into(),
"Not Allowed".to_string(),
)))
}
}
본 글은 CC BY-SA 4.0 라이선스로 배포됩니다. 공유 또는 변경 시 반드시 출처를 남겨주시기 바랍니다.